德国数学家David Hilbert在1891年构造了一种曲线,首先把一个正方形等分成四个小正方形,依次从西北角的正方形中心出发往南到西南正方形中心,再往东到东南角的正方形中心,再往北到东北角正方形中心,这是一次迭代;如果对四个小正方形继续上述过程,往下划分,反复进行,最终就得到一条可以填满整个正方形的曲线,这就是Hibert曲线。其生成过程如图1所示。

图1  Hilbert曲线的生成

Hilbert曲线可以采用递归过程实现,在递归处理时,连接中点的方式有4种,如图2所示。

图2  连接中心点的4种方式

设正方形左上角的顶点坐标为(x1,y1),右下角顶点坐标为(x2,y2)。若将方式(3)的正方形左上角坐标置为(x2,y2),右下角坐标置为(x1,y1),则方式(3)等同于方式(1),相当于旋转180°;同理,方式(4)等同于方式(2)。因此,4种连接中心点的方式可以看成(1)和(2)两种。

两种连线方式的连线走向及下一次扩展的方式如图3所示。

图3  两种连线方式走向及扩展

其中,方式(1)的四个中心点坐标分别为:

①(x1+dx/4,y1+dy/4)           ②(x1+dx/4, y1+3*dy/4)

③ (x1+3*dx/4, y1+3*dy/4)     ④(x1+3*dx/4,y1+dy/4)   (dx=x2-1,dy=y2-y1)

方式(2)的四个中心点坐标分别为:

①(x1+dx/4,y1+dy/4)           ②(x1+3*dx/4,y1+dy/4)

③ (x1+3*dx/4, y1+3*dy/4)     ④(x1+dx/4,  y1+3*dy/4)

为此,引入一个标识变量s,s=1表示方式(1),s=-1表示方式(2),这样两种方式的中心点坐标可以统一表示为:

①(x1+dx/4,y1+dy/4)        ②(x1+(2-s)*dx/4,  y1+(2+s)*dy/4)

③(x1+3*dx/4, y1+3*dy/4)      ④(x1+(2+s)*dx/4,y1+(2-s)*dy/4)

递归扩展时,方式(1)中4个小正方形的扩展方式分别是:方式(2)、方式(1)、方式(1)和方式(4)(注意:给定两个顶点坐标顺序调整后等同于方式(2));方式(2)中4个小正方形的扩展方式分别是:方式(1)、方式(2)、方式(2)和方式(3)。

编写如下的HTML代码。

<!DOCTYPE html>

<head>

<title>Hilbert曲线</title>

</head>

<body>

<canvas id="myCanvas" width="500" height="500" style="border:3px double #996633;">

</canvas>

<script type="text/javascript">

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext('2d');

var depth=5;

ctx.lineWidth = 2;

ctx.strokeStyle = "red";

ctx.beginPath();

ctx.moveTo(50+400/Math.pow(2,depth+1),50+400/Math.pow(2,depth+1));

drawShapes(depth,1,50,50,450,450);

ctx.stroke();

function drawShapes(n,s,x1,y1,x2,y2)

{

dx = x2 - x1,

dy = y2 - y1;

if (n>1)

{

if(s>0)

{

drawShapes(n-1,-1,x1,y1,(x1+x2)/2,(y1+y2)/2);

drawShapes(n-1,1,x1,(y1+y2)/2,(x1+x2)/2,y2);

drawShapes(n-1,1,(x1+x2)/2,(y1+y2)/2,x2,y2);

drawShapes(n-1,-1,x2,(y1+y2)/2,(x1+x2)/2,y1);

}

else

{

drawShapes(n-1,1,x1,y1,(x1+x2)/2,(y1+y2)/2);

drawShapes(n-1,-1,(x1+x2)/2,y1,x2,(y1+y2)/2);

drawShapes(n-1,-1,(x1+x2)/2,(y1+y2)/2,x2,y2);

drawShapes(n-1,1,(x1+x2)/2,y2,x1,(y1+y2)/2);

}

}

if (n==1)

{

ctx.lineTo(x1+dx/4,y1+dy/4);

ctx.lineTo(x1+(2-s)*dx/4,  y1+(2+s)*dy/4);

ctx.lineTo(x1+3*dx/4, y1+3*dy/4);

ctx.lineTo(x1+(2+s)*dx/4,y1+(2-s)*dy/4);

}

}

</script>

</body>

</html>

在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中绘制出如图4所示的Hilbert曲线。

图4  递归深度maxdepth =5的Hilbert曲线

上面的程序需要推出方式(一)和方式(二)的坐标统一形式,还需注意方式(3)和方式(4)与方式(一)和方式(二)的同一性。

由于Hilbert曲线可以看成是4种方式进行组合,因此可以直接对4种方式编写递归过程。编写如下的HTML文件。

<!DOCTYPE html>

<head>

<title>Hilbert曲线</title>

</head>

<body>

<canvas id="myCanvas" width="500" height="500" style="border:3px double #996633;">

</canvas>

<script type="text/javascript">

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext('2d');

ctx.lineWidth = 2;

ctx.strokeStyle = "red";

ctx.beginPath();

var depth=5;    //  递归深度

var h=400/Math.pow(2,depth);

var x = 50+h;

var y = 50+h;

ctx.moveTo(x,y);

One(depth);

ctx.stroke();

function One(n)   // 方式(1)的递归调用

{

if(n > 0)

{

Two(n-1);

ctx.lineTo(x, y+h); y+=h;

One(n-1);

ctx.lineTo(x+h, y); x+=h;

One(n-1);

ctx.lineTo(x, y-h); y-=h;

Four(n-1);

}

}

function Two(n)   // 方式(2)的递归调用

{

if(n > 0)

{

One(n-1);

ctx.lineTo(x+h, y); x+=h;

Two(n-1);

ctx.lineTo(x, y+h); y+=h;

Two(n-1);

ctx.lineTo(x-h, y); x-=h;

Three(n-1);

}

}

function Three(n)   // 方式(3)的递归调用

{

if(n > 0)

{

Four(n-1);

ctx.lineTo(x, y-h);  y-=h;

Three(n-1);

ctx.lineTo(x-h, y);  x-=h;

Three(n-1);

ctx.lineTo(x, y+h);  y+=h;

Two(n-1);

}

}

function Four(n)  // 方式(4)的递归调用

{

if(n > 0)

{

Three(n-1);

ctx.lineTo(x-h,y);      x-=h;

Four(n-1);

ctx.lineTo(x, y-h);     y-=h;

Four(n-1);

ctx.lineTo(x+h, y); x+=h;

One(n-1);

}

}

</script>

</body>

</html>

在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中绘制出如图5所示的Hilbert曲线。

图5  调用One(depth)时绘制的图形

将程序中的调用语句“One(depth)”改写成“Two(depth)”,则在浏览器窗口中绘制出如图6所示的Hilbert曲线。这个图形可以看成是图5向左旋转90°得到的。实际上,由图2可知,将方式(一)的图形向左旋转90°得到的就是方式(二)的图形。

图6  调用Two(depth)时绘制的图形

将程序中调用语句“One(depth)”改写成“Three(depth)”,同时修改初始坐标为

“var x = 450-h;   var y = 450-h;”,则在浏览器窗口中绘制出如图7所示的Hilbert曲线。

图7  调用THree(depth)时绘制的图形

将程序中调用语句“One(depth)”改写成“Four(depth);”,同时修改初始坐标为

“var x = 450-h;   var y = 450-h;”,则在浏览器窗口中绘制出如图8所示的Hilbert曲线。

图8  调用Four(depth)时绘制的图形

将Hilbert曲线的生成过程进行动画展示,编写如下的HTML代码。

<!DOCTYPE>

<html>

<head>

<title>Hilbert曲线</title>

</head>

<body>

<canvas id="myCanvas" width="500" height="500" style="border:3px double #996633;"></canvas>

<script type="text/javascript">

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext('2d');

var depth=1;

function drawShapes(n,s,x1,y1,x2,y2)

{

dx = x2 - x1,

dy = y2 - y1;

if (n>1)

{

if(s>0)

{

drawShapes(n-1,-1,x1,y1,(x1+x2)/2,(y1+y2)/2);

drawShapes(n-1,1,x1,(y1+y2)/2,(x1+x2)/2,y2);

drawShapes(n-1,1,(x1+x2)/2,(y1+y2)/2,x2,y2);

drawShapes(n-1,-1,x2,(y1+y2)/2,(x1+x2)/2,y1);

}

else

{

drawShapes(n-1,1,x1,y1,(x1+x2)/2,(y1+y2)/2);

drawShapes(n-1,-1,(x1+x2)/2,y1,x2,(y1+y2)/2);

drawShapes(n-1,-1,(x1+x2)/2,(y1+y2)/2,x2,y2);

drawShapes(n-1,1,(x1+x2)/2,y2,x1,(y1+y2)/2);

}

}

if (n==1)

{

ctx.lineTo(x1+dx/4,y1+dy/4);

ctx.lineTo(x1+(2-s)*dx/4,  y1+(2+s)*dy/4);

ctx.lineTo(x1+3*dx/4, y1+3*dy/4);

ctx.lineTo(x1+(2+s)*dx/4,y1+(2-s)*dy/4);

}

}

function go()

{

ctx.clearRect(0,0,canvas.width,canvas.height);

ctx.lineWidth = 2;

ctx.strokeStyle = "red";

ctx.beginPath();

ctx.moveTo(50+400/Math.pow(2,depth+1),50+400/Math.pow(2,depth+1));

drawShapes(depth,1,50,50,450,450);

ctx.stroke();

depth++;

if (depth>6)

{

depth=1;

}

}

window.setInterval('go()', 1000);

</script>

</body>

</html>

在浏览器中打开包含这段HTML代码的html文件,可以看到在浏览器窗口中呈现出如图9所示的Hilbert曲线动态生成效果。

图9  Hilbert曲线动态生成

JavaScript图形实例:Hilbert曲线的更多相关文章

  1. JavaScript图形实例:线段构图

    在“JavaScript图形实例:四瓣花型图案”和“JavaScript图形实例:蝴蝶结图案”中,我们绘制图形时,主要采用的方法是先根据给定的曲线参数方程计算出两点坐标,然后将两点用线段连接起来,线段 ...

  2. JavaScript动画实例:曲线的绘制

    在“JavaScript图形实例:曲线方程”一文中,我们给出了15个曲线方程绘制图形的实例.这些曲线都是根据其曲线方程,在[0,2π]区间取一系列角度值,根据给定角度值计算对应的各点坐标,然后在计算出 ...

  3. JavaScript图形实例:再谈IFS生成图形

    在“JavaScript图形实例:迭代函数系统生成图形”一文中,我们介绍了采用迭代函数系统(Iterated Function System,IFS)创建分形图案的一些实例.在该文中,仿射变换函数W的 ...

  4. JavaScript图形实例:随机SierPinski三角形

    在“JavaScript图形实例:SierPinski三角形”中,我们介绍了SierPinski三角形的基本绘制方法,在“JavaScript图形实例:迭代函数系统生成图形”一文中,介绍了采用IFS方 ...

  5. JavaScript图形实例:Koch曲线

    Koch曲线的构造过程是:取一条长度为L0的直线段,将其三等分,保留两端的线段,将中间的一段改换成夹角为60度的两个等长直线:再将长度为L0/3的4个直线段分别进行三等分,并将它们中间的一段均改换成夹 ...

  6. JavaScript图形实例:四瓣花型图案

    设有坐标计算公式如下: X=L*(1+SIN(4α))*COS(α) Y=L*(1+SIN(4α))*SIN(α) 用循环依次取α值为0~2π,计算出X和Y,在canvas画布中对坐标位置(X,Y)描 ...

  7. JavaScript图形实例:图形的旋转变换

    旋转变换:图形上的各点绕一固定点沿圆周路径作转动称为旋转变换.可用旋转角表示旋转量的大小. 旋转变换通常约定以逆时针方向为正方向.最简单的旋转变换是以坐标原点(0,0)为旋转中心,这时,平面上一点P( ...

  8. JavaScript图形实例:圆内螺线

    数学中有各式各样富含诗意的曲线,螺旋线就是其中比较特别的一类.螺旋线这个名词来源于希腊文,它的原意是“旋卷”或“缠卷”.例如,平面螺旋线便是以一个固定点开始向外逐圈旋绕而形成的曲线. 阿基米德螺线和黄 ...

  9. JavaScript图形实例:正多边形

    圆心位于坐标原点,半径为R的圆的参数方程为 X=R*COS(θ) Y=R*SIN(θ) 在圆上取N个等分点,将这N个点首尾连接N条边,可以得到一个正N边形. 1.正多边形阵列 构造一个8行8列的正N( ...

随机推荐

  1. java实现第九届蓝桥杯最大乘积

    最大乘积 把 1~9 这9个数字分成两组,中间插入乘号, 有的时候,它们的乘积也只包含1~9这9个数字,而且每个数字只出现1次. 比如: 984672 * 351 = 345619872 98751 ...

  2. java实现第五届蓝桥杯猜字母

    猜字母 题目描述 把abcd-s共19个字母组成的序列重复拼接106次,得到长度为2014的串. 接下来删除第1个字母(即开头的字母a),以及第3个,第5个等所有奇数位置的字母. 得到的新串再进行删除 ...

  3. 【Vlog】Jmeter之使用beanshell将json提取器中的多个值拼接为一个列表

    场景如下: json提取器返回了当前登录用户的所有好友id,然而下一个接口是把好友id拼成一个数组进行传参的,现需将所有的好友ID拼接起来,类似ID1,ID2,ID3......这样 beanshel ...

  4. 一个static和面试官扯了一个小时,舌战加强版

    一:背景 1. 讲故事 最近也是奇怪,在社区里看到好几篇文章聊static 的玩法以及怎么拿这个和面试官扯半个小时,有点意思,点进去看都是java版的,这就没意思了,怎么也得有一篇和面试官扯C# 中的 ...

  5. Spire.Cloud.SDK for Java 合并、拆分Excel单元格

    Spire.Cloud.SDK for Java 是Spire.Cloud云产品系列中,用于处理Word.Excel.PowerPoint以及PDF文档的JAR文件,可执行文档编辑.转换.保存等操作. ...

  6. https绕过证书认证请求 Get或Post请求(证书过期,忽略证书)

    报错信息 解决: postman方式 java请求 报错信息 javax.net.ssl.SSLHandshakeException: sun.security.validator.Validator ...

  7. 「从零单排canal 02」canal集群版 + admin控制台 最新搭建姿势(基于1.1.4版本)

    canal [kə'næl],译意为水道/管道/沟渠,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据 订阅 和 消费.应该是阿里云DTS(Data Transfer Service)的开 ...

  8. 4-react 组件之间传值。

    1.传值: 都是使用props和state结合得方式进行组件之间得传值 再react中传值得话是父子组件之间得传值,一般会把父组件中得state值通过props传给子组件,再子组件中使用props进行 ...

  9. 1.Go 开始搞起

    link 1. IDE Go Land 服务器激活 2. 资源 中文网站 翻译组 翻译组wiki 待认领文章 入门指南 中文文档 fork 更新 github 中如何定期使用项目仓库内容更新自己 fo ...

  10. PyQt5 FileDialog的使用例子

    加载***.ui文件可以使用: loadUi('main_window.ui', self) self.btnFileChoose.clicked.connect(self.getFolderName ...