JavaScript中的曲线相交点

时间:2017-11-22 18:20:25

标签: javascript canvas intersection curve

我希望通过向上或向下移动像素列来修改图像,使每列偏移跟随曲线。

我希望曲线以一些平滑的方式与6个左右的点相交。我想象一下,在图像x坐标上循环并调用一个曲线函数,该函数返回该偏移处曲线的y坐标,从而告诉我移动每列像素的数量。

我已经研究了各种类型的曲线,但坦率地说,我有点失落,我希望有一个现成的解决方案,可以让我插入我的点数据并吐出我需要的数据。我并不太习惯使用什么样的曲线,只要它看起来“光滑”。

任何人都可以帮我吗?

我正在使用HTML5和canvas。给出的答案here看起来像我追求的那种东西,但它指的是一个R库(我猜),这对我来说是希腊语!

1 个答案:

答案 0 :(得分:0)

Sigmoid曲线

一个非常简单的解决方案,如果你只想在y方向上的曲线是使用sigmoid曲线来插入控制点之间的y pos

// where 0 <= x <= 1 and p > 1
// return value between 0 and 1 inclusive.
// p is power and determines the amount of curve
function sigmoidCurve(x, p){
    x = x < 0 ? 0 : x > 1 ? 1 : x;
    var xx = Math.pow(x, p);
    return xx / (xx + Math.pow(1 - x, p))    
}

如果您希望x坐标px处的y位置位于两个控制点x1y1x2之间,y2

首先在pxx1

之间找到x2的标准化位置
var nx = (px - x1) / (x2 - x1); // normalised dist between points

将值插入sigmoidCurve

var c = sigmoidCurve(nx, 2); // curve power 2

使用该值来计算y

var py = (y2 - y1) * c + y1;

你在点之间的曲线上有一个点。

作为单个表达式

var py = (y2 - y1) *sigmoidCurve((px - x1) / (x2 - x1), 2) + y1;

如果将sigmoid曲线的功率设置为1.5,那么它几乎是一个三次贝塞尔曲线的完美匹配

实施例

此示例显示动画曲线。函数getPointOnCurve将获得位置x

处曲线上任意点的y pos

&#13;
&#13;
const ctx = canvas.getContext("2d");
const curve = [[10, 0], [120, 100], [200, 50], [290, 150]];
const pos = {};
function  cubicInterpolation(x, p0, p1, p2, p3){
    x = x < 0 ? 0 : x > 1 ? 1 : x;
    return p1 + 0.5*x*(p2 - p0 + x*(2*p0 - 5*p1 + 4*p2 - p3 + x*(3*(p1 - p2) + p3 - p0)));   
}
function sigmoidCurve(x, p){
    x = x < 0 ? 0 : x > 1 ? 1 : x;
	var xx = Math.pow(x, p);
	return xx / (xx + Math.pow(1 - x, p))    
}
// functional for loop
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); };
// functional iterator 
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };


// find index for x in curve
// returns pos{ index, y }
// if x is at a control point then return the y value and index set to -1
// if not at control point the index is of the point befor x
function getPosOnCurve(x,curve, pos = {}){
  var len = curve.length;
  var i;
  pos.index = -1;
  pos.y = null;
  if(x <= curve[0][0]) { return (pos.y = curve[0][1], pos) }
  if(x >= curve[len - 1][0]) { return  (pos.y = curve[len - 1][1], pos) }
  i = 0;
  var found = false;
  while(!found){  // some JS optimisers will mark "Do not optimise" 
                  // code that do not have an exit clause.
    if(curve[i++][0] <x && curve[i][0] >= x) { break }
  }
  i -= 1;
  if(x === curve[i][0]) { return (pos.y = curve[i][1], pos) }
  pos.index =i
  return pos;
}
// Using Cubic interpolation to create the curve
function getPointOnCubicCurve(x, curve, power){
  getPosOnCurve(x, curve, pos);
  if(pos.index === -1) { return pos.y };
  var i = pos.index;
  // get interpolation values for points around x
  var p0,p1,p2,p3;
  p1 = curve[i][1];
  p2 = curve[i+1][1];
  p0 = i === 0 ? p1 : curve[i-1][1];
  p3 = i === curve.length - 2 ? p2 : curve[i+2][1];
  // get unit distance of x between curve i, i+1
  var ux = (x - curve[i][0]) / (curve[i + 1][0] - curve[i][0]);
  return cubicInterpolation(ux, p0, p1, p2, p3);
}




// Using Sigmoid function to get curve.
// power changes curve power = 1 is line power > 1 tangents become longer
// With the power set to 1.5 this is almost a perfect match for
// cubic bezier solution.
function getPointOnCurve(x, curve, power){
  getPosOnCurve(x, curve, pos);
  if(pos.index === -1) { return pos.y };
  var i = pos.index;
  var p = sigmoidCurve((x - curve[i][0]) / (curve[i + 1][0] - curve[i][0]) ,power);
  return curve[i][1] + (curve[i + 1][1] - curve[i][1]) * p;
}

const step = 2;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;  // center width and height
var ch = h / 2;
function update(timer){
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
		ctx.clearRect(0,0,w,h);
    eachOf(curve, (point) => {
      point[1] = Math.sin(timer / (((point[0] + 10) % 71) * 100) ) * ch * 0.8 + ch;
    });
    
    ctx.strokeStyle = "black";
    ctx.beginPath();
    doFor(w / step, x => { ctx.lineTo(x * step, getPointOnCurve(x * step, curve, 1.5) - 10)});
    ctx.stroke();
    ctx.strokeStyle = "blue";
    ctx.beginPath();
    doFor(w / step, x => { ctx.lineTo(x * step, getPointOnCubicCurve(x * step, curve, 1.5) + 10)});
    ctx.stroke();    

    
    ctx.strokeStyle = "black";
    eachOf(curve,point => ctx.strokeRect(point[0] - 2,point[1] - 2 - 10, 4, 4) );
    eachOf(curve,point => ctx.strokeRect(point[0] - 2,point[1] - 2 + 10, 4, 4) );
    requestAnimationFrame(update);
}
requestAnimationFrame(update);
&#13;
canvas { border : 2px solid black; }
&#13;
<canvas id="canvas"></canvas>
&#13;
&#13;
&#13;

<强>更新

我在上面的演示中添加了第二种曲线类型,因为蓝色曲线偏离了原始的S形曲线黑色。

立方多项式

上述功能可适用于各种插值方法。我添加了函数

function  cubicInterpolation(x, p0, p1, p2, p3){
    x = x < 0 ? 0 : x > 1 ? 1 : x;
    return p1 + 0.5*x*(p2 - p0 + x*(2*p0 - 5*p1 + 4*p2 - p3 + x*(3*(p1 - p2) + p3 - p0)));   
}

根据x两侧两点的线斜率生成曲线。此方法适用于均匀间隔的点,但如果间距不均匀(例如此示例),则仍然有效。如果间距变得太不均匀,您可以注意到该点处的曲线有点扭结。

此外,拍摄过程中的曲线可能是一个问题。

有关Maths of cubic interpolation.

的更多信息