基於 WebGL 的 3D 電信機架之資料繫結

圖撲軟體發表於2019-03-15

前言

在前端中,檢視層和資料層需要進行單向或者雙向資料繫結,大家都已經不陌生了,有時候 2D 做的比較順了之後,就會想要挑戰一下 3D,不然總覺得癢癢的。這個 3D 機架的 Demo 我覺得非常有代表性,首先,3D 機架用途非常廣,尤其是在電信行業,就算不是機架,在比如工業方面 3D 模型以及資料繫結的應用也是非常廣泛的,畢竟現在工業物聯網已經是大趨勢了。

效果圖:
圖片描述

上面動圖中,閃爍燈是在不斷變化的,由於需要顯示的效果美觀一點,也實際一點,我截的圖還是比較完整的,但是這個閃爍的部分有點看不清楚(cnblog 中放太明顯的外鏈容易被移出首頁啊!!!等會再發!)。

程式碼實現

雖然上面 gif 圖中顯示的一個是 2D 的一個是 3D 的,但是構建的步驟以及需要的內容是一樣的,所以本文只針對 3D 的模型進行程式碼實現。

場景搭建

搭建一個 3D 場景是非常快速的,只需要三行程式碼:

dm = new ht.DataModel(); // 建立一個資料容器 資料容器也可以通過 g3d.getDataModel() 獲取
g3d = new ht.graph3d.Graph3dView(dm); // 建立一個 3D 場景,將資料容器作為引數傳遞進去,這樣資料容器中的內容就可以顯示在 3D 場景中了
g3d.addToDOM(); // 將 3D 場景新增到 body 體中

3D 機架模型構建

雖然可以叫設計師直接給我一個 obj 格式的模型,但是我覺得這個比較簡單,還是不要麻煩人家了。。。首先是建立一個六面體,模型上面的貼圖是我以前用的一個 json 格式的檔案,用來作為這個六面體的正面貼圖,這些部分都是寫在 json 檔案裡面的,我先擷取一小部分的 json 內容,然後用 js 程式碼復現:

{
  "c": "ht.Node", // 一個 ht.Node 型別的元素
  "i": 1277, // id
  "p": { // 通過 set/get 來設定/獲取的元素的部分。如 setPosition/getPosition
    "tag": "service", // 設定元素標籤 用來作為唯一標識
    "image": "symbols/機櫃.json", // 設定節點圖片
    "rotationX": 1.5708, // 設定節點 X 軸旋轉角度
    "position": { // 設定節點位置
      "x": 0,
      "y": 225
    },
    "anchor": { // 設定節點錨點
      "x": 0.5,
      "y": 0.54
    },
    "anchorElevation": 1, // 設定節點 y 軸錨點
    "width": 507, // 設定節點寬度
    "height": 980, // 設定節點長度
    "tall": 450, // 設定節點高度
    "elevation": 451 // 控制 Node 圖元中心位置所在 3D 座標系的y軸位置
  },
  "s": { // 設定圖元的 style 樣式,HT 預定義的一些樣式屬性,通過 node.s('all.color') 獲取和設定節點的樣式
    "all.color": "#DDDDDD", // 設定節點六面顏色
    "top.image": "symbols/機櫃.json", // 設定節點頂部圖片
    "front.visible": true, // 設定節點正面是否可見
    "back.visible": true,
    "left.visible": true,
    "right.visible": true,
    "bottom.visible": true
  }
}

這部分的 json 內容大體上就是建立了一個 ht.Node 節點,然後對這個節點設定了一些屬性,包括節點座標,節點的大小,以及一些 style 樣式設定。

那麼,如何用程式碼來建立這樣一個節點呢?

var node = new ht.Node(); // 建立一個 ht.Node 型別的節點
node.setTag('service'); // 設定節點的標籤
node.setImage('symbols/機櫃.json'); // 設定節點圖片
node.setRotationX(Math.PI/2); // 設定節點 x 軸旋轉
node.setPosition(0, 225); // 設定節點位置
node.setAnchor(0.5, 0.54); // 設定節點錨點
node.setAnchorElevation(1); // 設定節點 y 軸方向的錨點

node.setWidth(507); // 設定節點的寬度
node.setHeight(980); // 設定節點的長度
node.Tall(450); // 設定節點的高度
node.setElevation(451); // 控制 Node 圖元中心位置所在 3D 座標系的 y 軸位置

node.s({ // 設定節點樣式
    'all.color': '#ddd', // 六面顏色
    'top.image': 'symbols/機櫃.json', // 節點頂部圖片
    'front.visible': true, // 設定節點正面可見
    'back.visible': true,
    'left.visible': true,
    'right.visible': true,
    'bottom.visible': true
});

其實整個 json 就是由多個這種型別的圖元組合而成的。我們來拆析一下,整個 3D 機架實際上是由十個圖元組合而成的,第一個是整體的 3D 機櫃(也就是我們上面 json 內容中建立的部分),剩下的九個都是需要動態變化閃爍燈的裝置,也就是我紅框框起來的部分:

圖片描述

http://www.hightopo.com/demo/serviceShow/
這些裝置的建立方式跟上面的 3D 機架是類似的,只不過對應的節點尺寸小點,貼圖不一樣,座標不一樣罷了。但是下面的這九個節點的貼圖似乎有點不一樣?上面有閃爍的燈,並且不止一盞!怎麼動態獲取他們呢?

向量–資料繫結

不得不說到向量這個概念。向量在 HT for Web 中是向量圖形的簡稱,常見的 png 和 jpg 這類的柵格點陣圖, 通過儲存每個畫素的顏色資訊來描述圖形,這種方式的圖片在拉伸放大或縮小時會出現圖形模糊,線條變粗出現鋸齒等問題。 而向量圖片通過點、線和多邊形來描述圖形,因此在無限放大和縮小圖片的情況下依然能保持一致的精確度。

這些有點都是次要的,最重要的是這個向量可以進行資料繫結(這個資料繫結是繫結到節點中的),而且繫結方式非常容易!

向量採用 json 格式描述,使用方式和普通的柵格點陣圖一致,通過 node.setImage('hightopo.json') 或者 node.setIcon('hightopo.json') 等設定到資料模型中。

向量 json 描述必需包含 width、height 和 comps 引數資訊:

  • width 向量圖形的寬度
  • height 向量圖形的高度
  • comps 向量圖形的元件 Array 陣列,每個陣列物件為一個獨立的元件型別,陣列的順序為元件繪製先後順序

由於這張圖繪製的還是比較複雜的,所以我就將設定了資料繫結的矩形部分的向量繪製程式碼貼上出來:

{
    "width": 48, // 一個向量圖示必備的寬度 向量詳細內容請參考 HT for Web 向量手冊
    "height": 262, // 一個向量圖示必備的高度
    "comps": [ // 一個向量圖示必備的 Array 陣列元件
        { // 陣列元件中的第一個元素
            "type": "rect", // 型別為矩形
            "background": { // 設定矩形背景色
                "func": "attr@rectBg1", // HT 用一個帶 func 屬性的物件替換以前的引數值 這裡就是進行資料繫結的地方
                "value": "rgb(255,0,0)" // 如果 func 值為 undefined 或者 null 時,採用這個值
            },
            "shadow": true, // 設定“陰影”
            "shadowColor": { // “陰影”顏色
                "func": "attr@shadowColor1", // 這邊將“陰影”也進行了資料繫結,為的是能夠實現燈“發光”的效果
                "value": "rgba(255,0,0,0.35)" // 設定備選值
            },
            "shadowOffsetX": 0, // 設定陰影橫軸偏移量
            "shadowOffsetY": 0, // 設定陰影縱軸偏移量
            "rect": [ // 設定該元件的寬高以及座標
                4.38544, // x 軸座標
                23.52679, // y 軸座標
               14.46481, // width 元件寬度
               6.1554 // height 元件高度
           ]
       }
    ]
}

因為一個向量圖形中有 5 個“閃爍燈”,所以我新增了 5 個元件,也就是在 comps 引數裡面新增了五個元素,繫結的資料不同,為了省事,我將繫結的資料名都設定為“rectBg”後面加一個數字,這些數字依次遞增。

我們就是將這樣一張向量圖設定為節點的 front.image 作為節點正面顯示圖的:node.s('front.image', 'symbols/內部裝置2.json')

JSON 反序列化

會不會有人好奇 json 檔案裡面的內容是如何轉換成 3D 模型的?

說實在的,步驟依然很簡單:

ht.Default.xhrLoad('scene/service3d.json', function(text) { // xhrLoad 函式是一個非同步載入檔案的函式
    dm.deserialize(text); // 反序列化資料容器,解析用於生成對應的 Data 物件並新增到資料容器 這裡相當於把 json 檔案中生成的 ht.Node 節點反序列化到資料容器中,這樣資料容器中就有這個節點了
});

由於 xhrLoad 函式是一個非同步載入函式,所以如果 dm 資料容器反序列化未完成就直接呼叫了其中的節點,那麼會造成資料獲取不到的結果,所以一般來說我是將一些邏輯程式碼寫在這個函式內部,或者給邏輯程式碼設定 timeout 錯開時間差。

首先,由於資料都是儲存在 dm 資料容器中的(通過 dm.add(node) 新增的),所以我們要獲取資料除了可以通過 id、tag 等獨立的方式,還可以通過遍歷資料容器來獲取多個元素:

var infos = [
    { shadowColor: 'shadowColor1', background: 'rectBg1' },
    { shadowColor: 'shadowColor2', background: 'rectBg2' },
    { shadowColor: 'shadowColor3', background: 'rectBg3' },
    { shadowColor: 'shadowColor4', background: 'rectBg4' },
    { shadowColor: 'shadowColor5', background: 'rectBg5' }
];
var len = infos.length; // 獲取陣列中的長度
var datas = dm.toDatas(function(d) { return d.getDisplayName() === 'device'; }); // 以過濾函式構建新的元素集合並返回(ht.List 陣列型別)
setInterval(function() {
    datas.forEach(function(data) {
        var info = infos[Math.floor(Math.random() * len)];
        var shadowName = info.shadowColor;
        var bgName = info.background;

        if (data.a(shadowName) === 'rgba(255, 0, 0, 0.35)') { // 若節點業務屬性“陰影”顏色為紅色,則設定為綠色
            data.a(shadowName, 'rgba(0, 255, 0, 0.35)');
        }
        else { // 若節點業務屬性“陰影”顏色為綠色,則設定為紅色
            data.a(shadowName, 'rgba(255, 0, 0, 0.35)');
        }

        if (data.a(bgName) === 'rgb(255, 0, 0)') { // 若節點業務屬性背景顏色為紅色,則設定為綠色
            data.a(bgName, 'rgb(0, 255, 0)');
        }
        else { // 若節點業務屬性背景顏色為綠色,則設定為紅色
            data.a(bgName, 'rgb(255, 0, 0)');
        }
    });
}, 1000);

結束語

整體來說這個 Demo 還是比較有價值的,一是快速實現了 3D 機櫃模型,二是對模型上的元素進行了資料繫結。只是想讓你們知道,清晰的圖片繪製沒有那麼難~ 3D 的世界沒有那麼難~ 資料繫結也沒有那麼難!我希望也能讓您發現這並不是件難事。

相關文章