基于HTML5的动态线
1 起因
某天晚上在微信公众号中看到一篇令人上瘾的科学动图,我对这类实验十分感兴趣,但自己并不是科学工作者,所以也就仅限于感兴趣为止。
不过其中有一张地球和金星的运动轨迹,如下图:

我觉得这个我可以实现,于是就抽出时间自己做了个类似的小东西。
2 结构和思路
动图的结构是内外两个同心圆,两个动点分别在两个同心圆上运动,间隔一定的时间将两点连线,最后就能组成想要的图案了。
正因为思路和结构十分简单,我才觉得自己能够实现出来。
事实上确实很容易实现。
3 Canvas
HTML5的<canvas>标签提供了绝好的画板,比自己实现BMP图像容易多了,而且可以通过JavaScript来做出动画效果。
我设置的画布是511×511的,这是为了让圆心在正中间,没有特殊的含义。
首先初始化成黑色。然后开始画图。
4 画线
在Canvas中改变某一点的颜色很容易。使用Canvas的经验得自于Milo Yip的用JavaScript玩转计算机图形学。
Color的代码也是借鉴他的实现方式,我也考虑了直接使用数组,但还是选择了使用更清晰的Color。
之后就是其他的组成元素,Point、Line和Circle。
画线是最难的。我自己写了一个画线的方法,用的就是最简单直观的算法。
根据起点和终点的坐标,和每条直线都是形如 y=ax+b 的函数这一点,根据每个点的横坐标计算纵坐标。
由于并非每个
x
所对应的Math.round(),让直线看起来更平滑。
但这个做法并不完全起效。
我通过随机生成终点和起点坐标的方式来测试这种算法的效果,发现有些直线很合理,但也有一些不那么合理,直线变成了稀疏的点阵。
于是我搜索了一下一般的图形引擎中是如何实现画线算法的,得到了Bresenham算法。
在复刻这个算法之前,我也明白了为什么我的算法并不一直有效。
我曾经也写过画线的算法,遇到了和现在一样的问题。尽管当时没有解决,但是意识到了问题所在:由于像素本身是稀疏的点阵(没有小数坐标),所以每个
x
所对应的
用更数学,或者更计算机科学一点的语言来说,就是当直线斜率的绝对值大于1时,一些x对应多个y值。
这时候需要将直线沿着 y=x (如果斜率小于-1,则是 y=−x )翻转一下,使得斜率重新纳入[-1, 1]的范围。
5 Bresenham算法
Bresenham算法是一组常用的图形算法之一,上边这个算法太过直观,所以实现起来虽然简单,但性能上有所欠缺。
Bresenham算法则使用另一种计算方式。
但画直线这种事情,无论怎么算,都是基于数学的,而数学上的一条直线就是一个一元一次函数。这一点没有变。
Bresenham算法更多的是将计算过程从浮点运算和除法运算变成了整数的加法。这一点对计算机的性能影响比较大——至少在早期,这种性能影响完全不能忽略。
通过 y=ax+b,(0<=a<=1) 可以得到以下推论:
当已知直线上一点
(x0,y0)
的时候,
x0+1
对应的值是:
这是很简单的计算。也就是说,横坐标每增加1,纵坐标需要增加斜率a。将这个增量称作误差,记作 error 。
就像最初的算法中为了保证直线的美观,使用到了Math.round()函数,Bresenham算法也需要判断纵坐标何时需要加一。
那么当误差
error
超过
0.5
的时候,
y
的值增加
当然,这样计算还是浮点计算,为了优化性能,需要将浮点计算变成整数计算。
在画线之前,我们已经知道了直线的起点和终点,设为 (x0,y0) 和 (x1,y1) ,这四个数都是整数。
斜率则是 (y0−y1)(x0−x1) 。
判断纵坐标加一的条件是 error>=0.5 。
此时 error 的累加因子变成了 (y0−y1)(x0−x1) ,那么两边同时乘以 |x0−x1| ,就得到了新的判断条件:
error>=0.5×|x0−x1|
这个值也不见得一定是整数,所以两侧再同乘以 2 :
经过这样的变换, error 的累加因子也不再是 (y0−y1)(x0−x1) ,而变成了 |y0−y1|×2 。
于是求新坐标的伪代码如下:
deltax = abs(x0 - x1)
deltay = abs(y0 - y1)
error = 0
y = y0
for x from x0 to x1, step = 1:
error = error + deltay*2
if error > deltax:
y = y + 1
error = 0
当然以上伪代码的前提是斜率在 [0,1] 之间,如果不再此区间,需要进行额外的转换。更详细的伪代码在维基百科上有。
我使用的JavaScript代码则来自StackOverflow社区上的一个答案。
Bresenham算法不仅包含了直线的画法,也包含了一些其他的图形,圆形当然也包括在内。所以我从某个博客上直接拿过来用了。
主要的思路是将一个圆分成八份,每次计算坐标可以画出8个点。
但其中计算使用了一些常数,这些常数的来历我没有仔细看,但博客中应该写到了。
6 运动的圆形
为了显示出来金星和地球,多加了两个实心圆形。这两个圆形就不是用canvas画出来的了。
如果使用canvas画,需要记录每次移动前的圆形内部的像素数据,虽然不是不能实现,但是很麻烦。
所以直接用两个<div>代替。使用两个<div>,通过绝对定位就可以使之浮现在canvas上方。在通过计算坐标,使其与动点的坐标一致就可以了。
后来又加入了一些可以设定运动参数的功能,都只是锦上添花的小东西,不做赘述了。
7 效果
最后附上效果链接:
转动的同心圆
有了以上链接,代码就不贴了。
PS. 啊,差点把初衷忘了。要想画出和动图中相似的图案,我找到的最接近的参数应该是Inner speed = 6.5, Outer speed = 4,起点都是270。
本文介绍了如何使用HTML5的Canvas标签结合Bresenham算法实现动态线的绘制。从起因、结构思路到Canvas的使用,详细讲解了画线算法和Bresenham算法的工作原理,并展示了最终的动画效果。

1095

被折叠的 条评论
为什么被折叠?



