基於 HTML5 Canvas 的 3D 渲染引擎構建生產管控系統

圖撲軟體發表於2020-04-06

前言

這一期為大家帶來一個非常好玩的 demo,我們製作一套自己的 3D 管道控制系統,運用了( http://www.hightopo.com )HT 的 Graph3dView 元件通過對 WebGL 底層技術的封裝,與 HT 其他元件一樣,基於 HT 統一的 DataModel 資料模型來驅動圖形顯示。

效果圖

此為 2D 主介面:

此為 3D 介面的部分分段演示:

由於 gif 上傳有大小限制,所以請大家務必去網頁感受和體驗,雙擊進口閥開始。 ( 戳我進入!

程式碼實現

主要教大家的是一種流程動畫的製作方式,我用到包括動畫在內的多種方法,下面我聽我慢慢道來。由於是 3D 介面,關於建立 3D 渲染引擎元件,視覺化呈現資料模型的三維環境場景我之前有講過,就是 dataModel 和 graph3dView。(後面用簡寫的dm,gv代替)

我們先對整體介面的基礎進行一下設定:

// 禁止拖動
gv.setMovableFunc(function() { return false })
// 設定眼睛
gv.setEye([-922, 1745, 4659])
// 設定中心點
gv.setCenter([98, 621, -318])

然後,把需要加動畫的閥都獲取到,我們按照步驟依次來,以免落下關鍵步:

var a = dm.getDataByTag('進口電動球閥')
var b = dm.getDataByTag('旁通閥')
var c = dm.getDataByTag('出口電動球閥')
...
...

可以開始我們的動畫設計了!我用的是 flyTo() 的方法,事實證明這種效果真的很不錯。我們要事先準備好所有的動畫組並把它們串聯在一起,我設計的開始演示是通過雙擊進口閥來控制。比如第一步,應該開啟將進口球閥由遠端控制轉為就地控制。所以,我們要讓鏡頭從這裡開始:

gv.mi(function (e) {
    if (e.kind === 'doubleClickData') {
        if (e.data.getTag() === '進口電動球閥') {
            gv.flyTo(n, {
                animation : true,
                direction : [-200, 0, 0],
                distance : 100
            })
            anim1()
        }
    }
})

注意 mi 是增加互動事件監聽器,addInteractorListener 的縮寫。

// 示例:
gv.mi(function (event) {
    // event 格式:
    {
        kind: 'clickData', // 事件型別
        data: data, // 事件相關的資料元素
        part: "part", // 事件的區域, icon、label 等
        event: e // html 原生事件
    }
})

這裡面 n 就是第一步的那個按鈕,再介紹一下這個方法的相關引數:
flyTo : 相機看向具體的節點或者節點列表,引數 (target, options),其中 options : 可選屬性,格式為物件({}),屬性包括有:

  • animation : 預設 false,是否啟用動畫,可以設定為 true 或者 false 或者 animation 動畫物件
  • center : 預設 undefined,新的場景 center 點,形如 [0,0,0](空的話,target 為一個則看向 node 中心,target 為列表則看向根據節點列表計算出來的中心)
  • direction : 預設 undefined,眼睛處於目標的方向(相對目標,受到目標自身旋轉影響),例如 [0,1,5] 在目標正面的斜向上
  • worldDirection : 預設 undefined,眼睛處於目標的方向(相對場景,不受目標旋轉影響),例如 [0,1,5] 在目標所在位置的斜向上
  • distance : 預設undefined(未定義的話則使用下面的 ratio 模式計算距離),浮點型別,表示眼睛跟中心的固定距離
  • ratio : 預設 0.8,浮點型別,表示眼睛跟中心的距離動態計算(例如 0.8 表示眼睛在上述方向上動態計算距離以將目標包圍盒的 8 個角全部適配到螢幕 80% 範圍內)

注意,direction跟worldDirection如果都不配置,則使用之前相機的角度保持不變化。

後面全部用到動畫,解釋一下。在 HT 的資料模型驅動圖形元件的設計架構下,動畫可理解為將某些屬性由起始值逐漸變到目標值的過程, HT 提供了 ht.Default.startAnim 的動畫函式。它支援 Frame-Based 和 Time-Based 兩種方式的動畫,Frame-Based 方式是使用者通過指定 frames 動畫幀數,以及 interval 動畫幀間隔引數控制動畫效果。

我用的是 Time-Based 方式,該方式使用者只需要指定 duration 的動畫週期的毫秒數即可,HT 將在指定的時間週期內完成動畫, 不同於 Frame-Based 方式有明確固定的幀數,即 action 函式被呼叫多少次,Time-Based 方式幀數或 action 函式被呼叫次數取決於系統環境, 一般來說系統配置更好的機器,更高效的瀏覽器則呼叫幀數越多,動畫過程更平滑。由於 js 語言無法精確控制 interval 時間間隔, 採用 Frame-Based 不能精確控制動畫時間週期,即使相同的 frames 和 interval 引數在不同的環境,可能會出現動畫週期差異較大的問題, 因此 HT 預設採用 Time-Based 的方式,如果不設定 duration 和 frames 引數,則 duration 引數將被系統自動設定為 ht.Default.animDuration 值。action 函式就是實現動畫過程中的屬性變化(變化引數和進度)。

緊接著我們要開始執行第一個動畫—— anim1() 了:

function anim1() {
    ht.Default.startAnim({
        duration: 2000,
        action: function (v, t) {
            // 讓旋鈕旋轉,改變其角度 r3
            n.r3(n.r3()[0] - 0.02, n.r3()[1], n.r3()[2])
        },
        finishFunc: function () { // 動畫結束後呼叫的函式
            gv.flyTo(a, {
                animation : true,
                direction : [-2000, 1000, 2000],
                distance : 1000
            })
            anim2()
        }
    })
}...

可以看到動畫結束後我們再次用到 flyTo() 向下一個步驟開關去拉近,然後再次執行它的動畫,以此類推,關於一套清晰的操作流程的動畫實現指日可待!

當所有步驟結束後我們應當將鏡頭拉回到最開始時的初始視角,所以我們要注意一點,在最開始的時候提前把位置複製一下:

var oEye = ht.Default.clone(gv.getEye())
var oCenter = ht.Default.clone(gv.getCenter())

這樣,在最後一個 finishiFunc 中我們還原位置:

gv.setEye(oEye)
gv.setCenter(oCenter)

最後,一個簡明的系統操作流程就做好了,想看不懂都難~

總結

HT For Web 提供完整的基於 HTML5 圖形介面元件庫。您可以輕鬆構建現代化的,跨桌面和移動終端的企業應用,無需擔憂跨平臺相容性,及觸屏手勢互動等棘手問題。也可用於快速建立和部署,高度可定製化,並具有強大互動功能的拓撲圖形及錶盤圖表等應用。HT for Web 非常適用於實時監控系統的介面呈現,廣泛應用於電信網路拓撲和裝置管理,以及電力、燃氣等工業自動化 ( HMI / SCADA ) 領域。

相關文章