JavaScript圖形實例:Hilbert曲線

来源:https://www.cnblogs.com/cs-whut/archive/2020/07/06/13257224.html
-Advertisement-
Play Games

德國數學家David Hilbert在1891年構造了一種曲線,首先把一個正方形等分成四個小正方形,依次從西北角的正方形中心出發往南到西南正方形中心,再往東到東南角的正方形中心,再往北到東北角正方形中心,這是一次迭代;如果對四個小正方形繼續上述過程,往下劃分,反覆進行,最終就得到一條可以填滿整個正方 ...


      德國數學家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曲線動態生成


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • mariadb的主從複製集群,預設情況下是把主庫上的所有庫進行複製,只要在主庫上產生寫操作,從庫基於主庫的二進位日誌做重放,從而實現把主庫的上的庫表複製到從庫;複製過濾器指的是我們僅複製一個或幾個資料庫相關的數據,而非所有;過濾器的作用就是來定義我們要複製那些庫,那些表,這種定義過濾器的方式叫白名... ...
  • SQL語言大致分為`DCL`、`DDL`、`DML`三種,本文主要介紹`MySQL 5.7`版本的`DCL`語句。 ...
  • 本文更新於2019-06-23,使用MySQL 5.7,操作系統為Deepin 15.4。 SQL語句 創建存儲過程或函數 創建存儲過程: CREATE PROCEDURE name ({[IN|OUT|INOUT] param type}[, ...]) [characteristic] body ...
  • 一、跨程式發送廣播 廣播是一種可以跨進程的通信方式; 我們來寫一個發送有序廣播的項目 首先先建立一個BroadcastTest3項目 然後寫一個接收廣播的類,繼承自BroadcastReceiver package com.example.broadcasttest3; import android ...
  • 如果在提交APPStore的時候,提交了加急,如果被拒了,還需要再提交加急嗎?答案:不需要。 ...
  • iOS行業在經歷了過去幾年的爆發期後,現在到了一個相對冷靜的時期,一個良幣驅逐劣幣、去偽存真的階段。只有持續的專註和付出,才能夠在激烈的競爭中脫穎而出,成為強者。正如狄更斯所言,“這是一個最壞的時代,也是最好的時代” 。 對於這些面試題,不要死記硬背,應該舉一反三,深刻理解實現機制(這也是科班和非科 ...
  • 新聞 Android的電話應用將能夠告訴你為什麼企業要給你打電話 Google Play Store可能會重新開始顯示應用更新通知 谷歌確認將推出新功能 對標蘋果AirDrop 谷歌新版SafetyNet可能會讓root和定製ROM走向終結 Android版Gboard輸入法正測試面向IM應用的自動 ...
  • H分形是由一個字母H演化出迷宮一樣場景的分形圖案,其構造過程是:取一個中心點(x,y),以此中心點繪製一條長為L的水平直線和兩條長為H的豎直直線,構成一個字母“H”的形狀;再以兩條豎直直線的上下共4個端點為中心點,分別繪製一條長為L/2的水平直線和兩條長為H/2的豎直直線;重覆以上操作直至達到要求的 ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...