【问题标题】:As3 Function for extrapolating points on a spline curve or Hermite Curve similar to keframe interpolation?用于在样条曲线或 Hermite 曲线上外插点的 As3 函数,类似于 keframe 插值?
【发布时间】:2011-08-26 14:02:52
【问题描述】:

我希望有人可以帮助我进行一些高级数据重新格式化。我希望的是一个函数,我可以在其中输入一个值以及一个关键位置数组,如下所示:

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

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

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

【问题讨论】:

    标签: flash actionscript-3 math geometry


    【解决方案1】:

    Hermite 曲线的方程是这样的:

    (通过Wikipedia

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

    1. p0 是第一个控制点
    2. m0 是第一个锚点
    3. p1 是第二个控制点
    4. m1 是第二个锚点

    所以,ActionScript 3.0 中的等式应该是这样的:

            /*
             * 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;
            }
    

    请注意,该方程被使用了两次:一个点的每个分量/维度一次:在本例中为 x,y。

    这是一个基本的演示:

    /**
     * Copyright George.Profenza ( http://wonderfl.net/user/George.Profenza )
     * MIT License ( http://www.opensource.org/licenses/mit-license.php )
     * Downloaded from: http://wonderfl.net/c/pTgv
     */
    
    package {
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.events.MouseEvent;
        import flash.geom.Point;
    
        /**
         * @author George Profenza
         */
        public class BasicHermite extends Sprite {
            private var percent:Number = 0;
            private var P0:Point = new Point(10,90);//1st control pt
            private var T0:Point = new Point(300,200);//1st anchor pt - NOTE! The anchors are relative to the controls
            private var P1:Point = new Point(400,90);//2nd control pt
            private var T1:Point = new Point(-100,400);//2nd control pt
            private var points:Array = [P0,T0,P1,T1];
            private var pointAtPercent:Point;
            
            public function BasicHermite() {
                init();
            }
            private function init():void{
                stage.doubleClickEnabled = true;
                stage.addEventListener(MouseEvent.DOUBLE_CLICK, reset);
                reset();
            }
            private function reset(event : MouseEvent = null) : void {
                P1.x = 200 + Math.random() * 200;//randomize a wee bit
                T1.x = Math.random() * 200 - 100;
                graphics.clear();
                percent = 0;
                this.addEventListener(Event.ENTER_FRAME, draw);
            }
            private function draw(event : Event) : void {
                pointAtPercent = hermite(percent, points);//compute point
                if(percent == 0) graphics.moveTo(pointAtPercent.x,pointAtPercent.y);//draw
                graphics.lineStyle(5,0x009900,percent);
                graphics.lineTo(pointAtPercent.x,pointAtPercent.y);
                percent += .015;//update percentage of traversal along curve
                if(percent >= 1) removeEventListener(Event.ENTER_FRAME, draw);//done
            }
            /*
             * 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;
            }
        }
    }
    

    不过,我还是有点担心,因为您的示例提到了 3 个点(2 个控制点和一个锚点)。

    三次曲线(Hermite/Catmull-Rom/etc.)有 2 个控制点和 2 个锚点(3 次方的方程)

    如果只需要一个控制点,则需要使用Quadratic Curve

    (来自 Adob​​e Actionscript 3 文档的图片)

    三次曲线:

    二次曲线:

    (来自维基百科的动画)

    二次方程是这样的:

    这将转化为:

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

    另外,我不清楚你的意思

    高级数据重新格式化

    代码 sn-ps 是写成代码的公式,但还有其他计算方法。

    二次贝塞尔曲线是由函数 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);
            }
    

    由于 actionscript 插值是如何实现的,代码看起来有点倒退,因为 actionscript 插值是如何实现的:“两点之间的插值级别。指示新点的位置,沿着 pt1 和 pt2 之间的线。如果f=1,返回pt1;如果f=0,返回pt2。"

    更新

    进一步困惑:

    关于您的问题:我的示例实际上提到了 3 个控件 点和0个锚点

    您是否试图简单地沿一系列线(多个点、0 个锚点...直线)获取当前 x 的 y 值?

    记住我不需要图表本身 - 我只需要一个点 简单地走在直线图/锯齿线上(没有任何曲线)?

    如果是这样,你可以这样做:

    1. 遍历路径的所有点并找到当前 x 值的线(这将是开始 x 位置小于给定 x 且线的结束 x 位置大于给定 x 的线)给定 x 位置)
    2. 计算从行首到给定 x 的 x 距离与整行的比值 (end.x-start.x)
    3. 使用此比率除以当前行的“高度”(end.y 和 start.y 之间的差异)并将其偏移 start.y,利用 similar triangles, according to Thales' Theorem

    这里有一个简单的草图来说明这个想法: 想象一个直角三角形,其中当前线是斜边(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

    【讨论】:

    • 哇!令人惊叹的工作 George - 看到你对 AS3 几何的方法真的很有趣。关于您的问题:我的示例实际上提到了 3 个控制点和 0 个锚点。锚给出的细节对于我的任务来说有点多余,所以我希望(并且应该提到)平均锚给出一个粗略的图表。请记住,我不需要图表本身 - 我只需要在上面指出一点,所以即使它的出色工作,它可能对我的需求来说有点沉重。
    • 实际上,最后的 quadLearp 函数看起来接近我需要的......我只需要评估一个点数组(不仅仅是 2 个)并理想地对它们进行样条曲线而不是简单地连接点.会考虑的...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-23
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-09
    相关资源
    最近更新 更多