Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何将 N 个点用光滑的曲线连接 #52

Open
ajccom opened this issue Jan 21, 2016 · 0 comments
Open

如何将 N 个点用光滑的曲线连接 #52

ajccom opened this issue Jan 21, 2016 · 0 comments

Comments

@ajccom
Copy link
Contributor

ajccom commented Jan 21, 2016

如何将 N 个点用光滑的曲线连接

在思考如何对线段进行噪化的时候,我设想了一种方案 —— 在直线上获取的 N 个点并用光滑的曲线连接点与点,使之变成一条不规则的波浪线。


##### 对比效果展示

l1l2

线段噪化前后对比

下面我就来讲述如何才能实现这个效果。


## 实现方案

我设计了一种基于二次贝塞尔曲线的解决方案,即通过二次贝塞尔曲线去连接分割直线后的 N 个散列点,所以我需要完成如下几个步骤才能得到最终效果:

  • 第一步,获取散列点作为每一段二次贝塞尔曲线的终点;
  • 第二步,设置第一段二次贝塞尔曲线的控制点;
  • 第三步,为了使曲线之间能够平滑的连接,需要获取每条曲线的特定控制点;
  • 第四步,绘制。

### 获取散列点

为了计算简单,我设计了一种对线段进行等分的散列点获取函数,它会返回被分割后线段所包含的所有的点。

/**
 * _getNoisePath 将路径噪化处理
 * @param {Number} dist 路径长度
 * @param {Number} interval 噪化点之间的间隙
 * @param {Number} f 幅度
 * @return {Array} 噪化路径的点集合
 *
 * 处理前需要将画布原点移动到路径起始点位置且旋转画布使x轴与线段重合
 */
function _getNoisePath (dist, interval, f) {
  var i = 1, l = Math.floor(dist / interval), d, result = [[0, 0]]
  for (i; i < l; i++) {
    result.push([interval * i, f * Math.random() * (Math.random() > 0.5 ? -1 : 1)])
  }
  result.push([dist, 0])
  return result
}

这个函数有个前提设定,即设定我们的线段是一条从原点开始并与 X 轴重合的水平线。这是为了方便处理数据,而后期在绘制的时候,我们需要先对坐标轴进行变换,将坐标轴的 X 轴与线段重合并且原点 (0, 0) 处与线段起点重合。

另外,函数中的 f 参数表示振幅,即该点可能偏离原线段路径的可能偏移值。


### 获取每条曲线的特定控制点

为了能够使曲线与曲线之间可以光滑的连接在一起,我们需要找到特定的二次贝塞尔曲线的控制点。而这个控制点,就是上一条曲线的控制点相对该曲线的终点的对称点。

c

当前曲线的控制点与前一个曲线的终点和控制点的关系
/**
 * _getSymmetryPoint 获取对称控制点
 * @param {Array} controlPoint 当前路径控制点
 * @param {Array} endPoint 当前路径终点
 * @return {Array} 对称点
 */
function _getSymmetryPoint (controlPoint, endPoint) {
  var x, y, x1 = controlPoint[0], y1 = controlPoint[1], x2 = endPoint[0], y2 = endPoint[1]
  x = 2 * x2 - x1
  y = 2 * y2 - y1
  return [x, y]
}

## 绘制

有了上面两个函数的铺垫,绘制出我们需要的最终效果就易如反掌了。

var ctx = canvas.getContext('2d'),
  width = canvas.width,
  height = canvas.height

//假设线段是由点 (100, 100) 和 点 (400, 400) 组成
//注意这里的 prevCtrl 变量,赋值的值是第一个曲线的控制点
var path = _getNoisePath(Math.sqrt(90000 + 90000), 100, 40),
  prevCtrl = [(path[1][0] - path[0][0]) / 2, 40]

ctx.beginPath()
ctx.translate(100, 100)
ctx.rotate(Math.atan(300/300))
ctx.moveTo(0, 0) // 坐标轴变换的好处多多
path.shift()
path.map(function (p, i) {
  ctx.quadraticCurveTo(prevCtrl[0], prevCtrl[1], p[0], p[1])
  prevCtrl = _getSymmetryPoint(prevCtrl, p) //获取下一条曲线的控制点
})
ctx.stroke()
ctx.closePath()

查看 DEMO


## 扩展阅读 - 坐标系变换

在日常生活中对“斜”着的线做计算总是很复杂,需要 X 轴与 Y 轴共同参与计算,而且两点之间的距离公式(a^2 + b^2 = c^2)也比较复杂。

而通过坐标系变换后,平移画布至线段起点,使画布原点与线段起点重合。再旋转画布,使 X 轴与线段重合,这样在计算线段本身长度(仅需要 X 轴参与计算)或垂直于该线段的线段的长度( Y 轴的值)时,取值都很简单方便。

当然了,你需要多做的就是平移和旋转坐标系。


## Thanks
@ajccom ajccom changed the title 如何将处在一直线上的 N 个点用光滑的曲线连接 如何将 N 个点用光滑的曲线连接 Jan 21, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants