用于在样条曲线或 Hermite 曲线上外插类似于 keframe 插值的点的 As3 函数? [英] As3 Function for extrapolating points on a spline curve or Hermite Curve similar to keframe interpolation?

查看:28
本文介绍了用于在样条曲线或 Hermite 曲线上外插类似于 keframe 插值的点的 As3 函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望有人能帮我解决一些高级数据重新格式化的问题.我希望的是一个函数,我可以在其中输入一个值以及一系列关键位置,如下所示:

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

数组将从最小值开始,以最大值结束,沿途嵌套关键点.例如,我会输入类似 remap(0.25, [0:0,80:50,100:100] ) 的内容,该函数将想象"一个来自 (0,0)-(100,100),关键点为 (80,50),然后返回沿该图占 25% 的 y 值.

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

解决方案

Hermite 曲线的方程是这样的:

(来自:

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

三次曲线:

二次曲线:

(来自维基百科的动画)

二次方程是这样的:

翻译成:

私有函数 quad(t:Number,p:Array):Point{var 结果: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;返回结果;}

还有一点测试代码:

包{导入 flash.display.Sprite;导入 flash.events.Event;导入 flash.events.MouseEvent;导入 flash.geom.Point;/*** @author 乔治*/公共类 BasicQuad 扩展了 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);私有变量 pts:Array = [p0,p1,p2];私有变量 t:Number = 0;私有变量 pt : 点;公共函数 BasicQuad() {在里面();}私有函数 init():void{stage.doubleClickEnabled = true;stage.addEventListener(MouseEvent.DOUBLE_CLICK, reset);重启();}私有函数重置(事件:MouseEvent = null):无效{图形清除();graphics.lineStyle(3,0x009900,.5);t = 0;this.addEventListener(Event.ENTER_FRAME, draw);}私有函数绘制(事件:事件):无效{跟踪(t,pt);pt = 四边形(t,pts);if(t == 0) graphics.moveTo(pt.x,pt.y);//绘制graphics.lineTo(pt.x,pt.y);t+= 0.015;if(t >= 1) removeEventListener(Event.ENTER_FRAME, draw);//done}私有函数 quad(t:Number,p:Array):Point{var 结果: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;返回结果;}}}

另外,我不清楚你的意思

<块引用>

高级数据重新格式化

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

<块引用>

二次贝塞尔曲线是由函数 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);返回 Point.interpolate(p2, p1, t);}

由于 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 抵消它,利用 类似三角形,根据泰勒斯定理

这是一个快速草图来说明这个想法:想象一个直角三角形,其中当前线是hypothenuse(ABC).现在想象一条来自鼠标光标的垂直线将该三角形分成两个相似的三角形(OO').小三角形和大三角形的角相同,边成比例.您使用 AO 和 AB 之间的比率除以 AC 并获得 OO' 的长度(该 x 在直线上的 y 位置).

功能如下:

私有函数 getYforX(x:Number,pts:Vector.):Number{var numPts:int = pts.length;for (var i: int = 1; i 

还有一个快速演示:

包{导入 flash.events.*;导入 flash.display.*;导入 flash.geom.Point;公共类 LerpPoints 扩展 Sprite {私有变量路径:Shape = new Shape();private var cursor:Shape = new Shape();私有变量 numPts:int = 11;私有变量 pts:Vector.= new Vector.(numPts,true);私有变量 t:Number = 0;公共函数 LerpPoints() {在里面();}私有函数 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);重启();addChild(path);addChild(cursor);addEventListener(Event.ENTER_FRAME, 更新);stage.addEventListener(MouseEvent.MOUSE_DOWN, 重置);}私有函数重置(事件:事件=空):无效{path.graphics.clear();for (var i : int = 0; i  0){//直角三角形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);}}}私有函数更新(事件:事件):无效{cursor.x = mouseX;cursor.y = getYforX(mouseX, pts);}私有函数 getYforX(x:Number,pts:Vector.):Number{var numPts:int = pts.length;for (var i: int = 1; i 

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

想到的一个肮脏的技巧是遍历点对并将 Y 值存储在查找表中.循环次数将是行详细信息"

再说一次,这让我很困惑:

<块引用>

只需要评估一组点(不仅仅是2个),理想情况下样条曲线它们而不是简单地连接点所以你有多个点,但是既然你提到了 0 个锚点,那么样条从哪里进来?

HTH

I was hoping someone could help me working out some advanced data reformatting. What I'm hoping for is a function where I can input a value along with an array of key positions like so:

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

The array would start with the minimum and end with the maximum point, with nested key points along the way. For example, I would input something like remap(0.25, [0:0,80:50,100:100] ) and the function would 'imagine' a spline curve graph from (0,0)-(100,100) with a key point of (80,50), then return the y value that is 25% along that graph.

Hopefully that's clear... Any ideas?

解决方案

The equation for the Hermite Curve is this:

(via Wikipedia)

Where p(t) is the point on the curve at t (percent 0.0 to 1.0)

  1. p0 is the first control point
  2. m0 is the first anchor point
  3. p1 is the second control point
  4. m1 is the second anchor point

So, the equation in ActionScript 3.0 would be something like this:

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

Notice the equation is used twice: once for each component/dimension of a point: x,y in this case.

Here's a basic demo:

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

Still, I am a bit concerned because you're example mentions 3 points (2 control points and one anchor point).

Cubic Curves(Hermite/Catmull-Rom/etc.) have 2 control points and 2 anchor points (equations at power of 3 - cubic)

If you only need one control point, you need to use a Quadratic Curve:

(Image from Adobe Actionscript 3 Documentation)

Cubic Curve:

Quadratic Curve:

(Animations from Wikipedia)

The Quadratic Equation is this:

Which would translate to:

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

And a bit of test code:

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

Also, am not clear what you mean by

advanced data reformatting

The code snippets are the formulas written as code, but there are other ways to compute this.

A quadratic Bézier curve is the path traced by the function B(t), given points P0, P1, and P2,

We need to go from P0 to P1 and from P1 to P2. Since you're looking for a Flash/ActionScript solution, we can take advantage of the Point's interpolate() method. So we interpolate between P0 and P1 to get let's say P01 then from P1 to P2 to get P12 and the interpolation through all 3 points will be the interpolation between P01 and 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);
        }

The code looks a bit backwards from what I wrote above because of how the actionscript interpolation is implemented: "The level of interpolation between the two points. Indicates where the new point will be, along the line between pt1 and pt2. If f=1, pt1 is returned; if f=0, pt2 is returned."

UPDATE

Further confused:

In reference to your question: my example actually mentions 3 control points and 0 anchor points

are you trying to simply get the y value of the current x along a series of lines (multiple points, 0 anchor points...straight lines) ?

Remember I dont need the graph itself - I just need a point on it Simply walk on a straight line graph/jagged line(no curves whatsoever) ?

If, so, you can do something like this:

  1. Loop through all the points of your path and find the line for the current x value (this will be the line for which the start x position is smaller than the given x and the end x position of the line is larger than the given x position)
  2. Calculate the ratio between the x distance from the start of the line to the given x and the whole line (end.x-start.x)
  3. Use this ratio to divide the current line 'height' (difference between end.y and start.y) and offset it by the start.y, taking advantage of similar triangles, according to Thales' Theorem

Here's a quick sketch to illustrate the idea: Imagine a right angled triangle where the current line is the hypothenuse(ABC). Now imagine a vertical line from your mouse cursor splitting that triangle into two similar triangle(OO'). The small triangle has the same angles as the large one and it's sides are proportional. You use the ratio between AO and AB to divide AC by and obtain the length of OO' (the y position on the line for that x).

Here's the function:

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

And a quick demo:

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

Note that this works if the x values in your points array are in sorted ascending (e.g. your path goes only left to right)

A dirty hack that comes mind is to loop through the pairs of points and store Y values in a lookup table. The number of loops woud be the 'line detail'

Then again, this confuses me:

just need to to asses an array of points (not just 2) and ideally spline curve them rather than simply joining the dots So you have multiple points, but where does the spline come in, since you mentioned 0 anchor points ?

HTH

这篇关于用于在样条曲线或 Hermite 曲线上外插类似于 keframe 插值的点的 As3 函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆