基于HTML5的动态线

来源:互联网 发布:网络缓存级别最高延迟 编辑:程序博客网 时间:2024/06/06 13:11

基于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所对应的y都是整数,但点的坐标必然都是整数,所以需要使用Math.round(),让直线看起来更平滑。

但这个做法并不完全起效。

我通过随机生成终点和起点坐标的方式来测试这种算法的效果,发现有些直线很合理,但也有一些不那么合理,直线变成了稀疏的点阵。

于是我搜索了一下一般的图形引擎中是如何实现画线算法的,得到了Bresenham算法

在复刻这个算法之前,我也明白了为什么我的算法并不一直有效。

我曾经也写过画线的算法,遇到了和现在一样的问题。尽管当时没有解决,但是意识到了问题所在:由于像素本身是稀疏的点阵(没有小数坐标),所以每个x所对应的y值并不一定是一个。

用更数学,或者更计算机科学一点的语言来说,就是当直线斜率的绝对值大于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对应的值是:

y1=a(x0+1)+b=ax0+b+a=y0+a

这是很简单的计算。也就是说,横坐标每增加1,纵坐标需要增加斜率a。将这个增量称作误差,记作error

就像最初的算法中为了保证直线的美观,使用到了Math.round()函数,Bresenham算法也需要判断纵坐标何时需要加一。

那么当误差error超过0.5的时候,y的值增加1,是一个比较恰当的选择,遵循四舍五入的原则。

当然,这样计算还是浮点计算,为了优化性能,需要将浮点计算变成整数计算。

在画线之前,我们已经知道了直线的起点和终点,设为(x0,y0)(x1,y1),这四个数都是整数。

斜率则是(y0y1)(x0x1)

判断纵坐标加一的条件是 error>=0.5

此时error的累加因子变成了(y0y1)(x0x1),那么两边同时乘以|x0x1|,就得到了新的判断条件:

error>=0.5×|x0x1|

这个值也不见得一定是整数,所以两侧再同乘以2

error>=|x0x1|

经过这样的变换,error的累加因子也不再是(y0y1)(x0x1),而变成了|y0y1|×2

于是求新坐标的伪代码如下:

deltax = abs(x0 - x1)deltay = abs(y0 - y1)error = 0y = y0for 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


0 0
原创粉丝点击