Hermite 曲线的方程是这样的:
(通过Wikipedia)
其中 p(t) 是曲线上 t 处的点(0.0 到 1.0 的百分比)
- p0 是第一个控制点
- m0 是第一个锚点
- p1 是第二个控制点
- 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:
(来自 Adobe 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 值?
记住我不需要图表本身 - 我只需要一个点
简单地走在直线图/锯齿线上(没有任何曲线)?
如果是这样,你可以这样做:
- 遍历路径的所有点并找到当前 x 值的线(这将是开始 x 位置小于给定 x 且线的结束 x 位置大于给定 x 的线)给定 x 位置)
- 计算从行首到给定 x 的 x 距离与整行的比值 (end.x-start.x)
- 使用此比率除以当前行的“高度”(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