基於 HTML5 + WebGL 的無人機 3D 可視化系統

  • 2020 年 2 月 11 日
  • 筆記

前言

近年來,無人機的發展越發迅速,既可民用於航拍,又可軍用於偵察,涉及行業廣泛,也被稱為「會飛的照相機」。但作為軍事使用,無人機的各項性能要求更加嚴格、重要。本系統則是通過 HightopoHT for Web 產品來搭建的一款 無人機 3D 可視化系統,通過對無人機及其信息的全景展示來模擬無人機狀態的監控。

系統中包含 4 種展示模式:實體模式 、熱力模式、線框模式和內部模式,通過飛機下方操作按鈕即可進行模式切換。

預覽地址:http://www.hightopo.com/demo/Drones/

實現過程

  • 加載界面採用 2D 拓撲組件進行繪製,全矢量化圖標,與傳統的 png、jpg 等格式的圖片相比,完美適配移動端、PC 端、大屏等各種尺寸及分辨率屏幕,不會出現失真情況。
  • 無人機及周邊信息面板採用 3D 引擎進行場景搭建,用戶可從場景任意位置對無人機進行查看。
  • 動畫過程採用產品提供的動畫函數 ht.Default.startAnim 來驅動圖形屬性值的改變,應用其 Time-Based 的方式,只需要指定動畫周期 duration 的毫秒數,由系統去計算幀數或 action 函數被調用的次數,以保證更加高效、平滑的進行動畫過程。

界面

加載界面中通過動態改變圖形的屬性值來展現加載進度,加載完畢後通過動畫的 finishFunc 調用 hidden2d 方法來改變圖形的透明度,在此之後通過 moveCamera 將場景內視角拉近,從而實現淡出到淡入的效果(即離開加載界面進入到 3D 場景中)。與此同時改變圖形在 3D 場景中位置來實現各形態的無人機合為一體以及將對應按鈕分離。

// 加載進度  function loadAnim(){    ht.Default.startAnim({      duration: 6000,      easing: t => {        return 1 - (--t) * t * t * t;      },      action: (v, t) => {        loge.s('clip.percentage', v);        percentText.s('text', Math.floor(v * 100));      },      finishFunc: () => {        hidden2d()      }    })  }    // 隱藏 2d 圖紙  function hidden2d(){    ht.Default.startAnim({      duration: 500,      easing: t => {        return t;      },      action: (v, t) => {        dm2d.each(e => {          e.s('opacity', 1 - v);        })      },      finishFunc: () => {        dm2d.setBackground('');        g3d.moveCamera(eye, center, {duration: 3000, easing: t => {return 1 - (--t) * t * t * t;}});        planeFit();        buttonSeparate();      }    })  }

2D 界面的製作是繪製了一張圖紙,而 logo 則是製作了一個圖標,一個圖標可以在圖紙中進行多次使用,並可展示不同的樣子。下圖中右側的四個 logo 就是同一個圖標,分別展示了不同的裁切方式以及透明度,系統中 logo 的進度條效果就是動態的去改變圖標的裁切比例來實現,而界面的淡出效果則是改變透明度。整個圖標的製作是繪製不同的圖形組合而成,這些圖形我們也可以去改變顏色,形成左側的 logo 樣式。

無人機形態切換

無人機主體形態分為三種:實體模式、線框模式和熱力模式。通過點擊下方按鈕,可切換至按鈕所對應的形態。切換的過程中,將目標形態進行顯示,並分別上下移動目標形態和原形態,使用戶可以短暫的進行同時查看,之後再回歸原位並將原形態進行隱藏。隱藏的方式則有所不用,線框模式是改變線框顏色,其餘兩種模式則是調整模型的透明度。這裡的線框是根據模型的輪廓生成的,通過 3D 引擎自動計算描繪,非常便捷。

// 選擇展示機型  function select(data){    const name = data.getDisplayName();    const moveData = dm3d.getDataByTag(name);    const normalP = normalPlane.p3();      ht.Default.startAnim({      duration: 1000,      easing: t => {        return 1 - (--t) * t * t * t;      },      action: (v, t) => {        if(name === 'linePlane'){          moveData.s('wf.color', 'rgba(64,173,152,' + v + ')');        }        else{          moveData.s('shape3d.opacity', v);        }        moveData.p3(normalP[0], normalP[1] + (4500 - normalP[1]) * v, normalP[2]);        isShow.p3(normalP[0], normalP[1] + (2500 - normalP[1]) * v, normalP[2]);          hiddenRing(v);      },      finishFunc: () => {        isShow.s('shape3d.transparent', true);          ht.Default.startAnim({          duration: 1000,          easing: t => {            return 1 - (--t) * t * t * t;          },          action: (v, t) => {            if(isShow.getTag() === 'linePlane'){              isShow.s('wf.color', 'rgba(64,173,152,' + (1 - v) + ')');            }            else{              isShow.s('shape3d.opacity', 1 - v);            }            moveData.p3(normalP[0], 4500 + (normalP[1] - 4500) * v, normalP[2]);            isShow.p3(normalP[0], 2500 + (normalP[1] - 2500) * v, normalP[2]);              showRing(v);          },          finishFunc: () => {            isShow = moveData;            if(moveData.getTag() === 'normalPlane'){              moveData.s('shape3d.transparent', false);            }            isAnim = false;          }        });      }    });  }

通過 3D 引擎,我們可以生成立體圖形,也可以添加導入的模型,圖形的位置由 x、y、z 三個方向的坐標來確認,而坐標軸匯聚的原點則是圖形的錨點,無人機前方旋轉的圓環則是將錨點調整到圓環中心後,操縱 rotation 屬性進行轉動 。在系統中線框狀態的無人機則是像圖中左側的球體這樣生成的,如果我們將圖形的透明度調為 0,則只顯示線框的樣式。

內部結構

在線框模式下,大家會發現按鈕的上方出現了一個小按鈕,點擊它就可以進入到無人機的另一個狀態,在這裡我們除了可以看到線框,還能夠接觸到無人機的內部結構,查看它的每一個部件。進入的過程會將場景內的其它圖形隱藏,將內部結構顯示出來。

// 內部模式  function moveToInternal(){    const width = background.getWidth();   // 獲取背景當前寬度    const beginLeft = -(width / 2 - 960);   // 計算兩側節點起始位置    const beginRight = width / 2 + 960;      ht.Default.startAnim({      duration: 2000,      easing: t => {        return t;      },      action: (v, t) => {        linePlane.s('wf.color', 'rgba(64,173,152,' + (1 - v) + ')');        hiddenRing(v);      },      finishFunc: () => {        dm3d.each(e => {            e.s('3d.visible', false)        })          linePlane_internal.each(e => {          e.s('3d.visible', true);        })          ht.Default.startAnim({          duration: 1000,          easing: t => {            return 1 - (--t) * t * t * t;          },          action: (v, t) => {            title.setY(-50 + (70 + 50) * v);            returnButton.setY(1095 + (1020 - 1095) * v);            leftShape.setX(beginLeft + 130 * v);            rightShape.setX(beginRight - 130 * v);          }        })      }    })  }

接下來,我們探索一下進入內部模式時,從四周緩緩移入窗口的圖形又是怎麼實現的

現如今,終端的屏幕分辨率各式各樣,很多網頁在開發前期都會選擇響應式布局或自適應式布局,而 HT for Web 的 2D 拓撲組件除了提供矢量繪製,還提供了一套相契合的布局方式。從上圖中我們可以看到,紅框中的部分是最開始的加載界面,而紅框四周的部分則是內部模式中移入的部分,系統中的加載頁面與此不同,正是因為添加了布局方式。

首先我們將紅框中背景圖片選為整個頁面根節點,修改它的 fullscreen 屬性為 fill,並為其添加全屏鎖定的方式。其次將根節點設置為其餘節點的吸附節點,並為其餘節點修改合適的布局方式。這樣背景圖片就會填充整個界面,而四周節點的位置則始終保持在背景圖片的外側,在加載界面中就不會顯示出來了。內部模式將四周節點移動至界面內,也是通過修改位置來實現的,但是因為全屏鎖定方式設置為垂直,所以背景的寬度被改變,左右兩側節點的移動則需要在獲取到當前界面顯示寬度後去計算移動位置。

總結

現如今,信息化快速發展,智能化工具成為了新生產力出現在我們的生活中。與此同時,工業互聯網的概念也隨之誕生,將人、數據、設備聯繫到了一起,而可視化界面則可以很好的將數據和設備進行展示,方便管理的同時也更加安全、高效。

HT for Web 提供了 2D、3D 兩種模式的可視化方式,它功能強大的同時也易於學習使用,有興趣的同學也可以來體驗一下。