基於 WebGl HTML5 的 3D 視覺化機房
前言
用 WebGL 渲染的 3D 機房現在也不是什麼新鮮事兒了,這篇文章的主要目的是說明一下,3D 機房中的 eye 和 center 的問題,剛好在專案中用上了,好生思考了一番,最終覺得這個例子最符合我的要求,就拿來作為記錄。
demo演示地址:http://hightopo.com/demo/3DRoom/index.html
效果圖:
這個 3D 機房的 Demo 做的還不錯,比較美觀,基礎的互動也都滿足,接下來看看怎麼實現。
程式碼生成
定義類
首先從 index.html 中呼叫的 js 路徑順序一個一個開啟對應的 js,server.js 中自定義了一個 Editor.Server 類由 HT 封裝的 ht.Default.def 函式建立的(注意,建立的類名 Editor.Server 前面的 Editor 不能用 E 來替代):
ht.Default.def('Editor.Server', Object, { // 第一個引數為類名,如果為字串,自動註冊到 HT 的 classMap 中;第二個引數為此類要繼承的父類;第三個引數為方法和變數的宣告
addToDataModel: function(dm) { // 將節點新增進資料容器
dm.add(this._node); // ht 中的預定義函式,將節點通過 add 方法新增進資料容器中
},
setHost: function() { // 設定吸附
this._node.setHost.apply(this._node, arguments);
},
s3: function() { // 設定節點的大小
this._node.s3.apply(this._node, arguments);
},
setElevation: function() { // 控制Node圖元中心位置所在3D座標系的y軸位置
this._node.setElevation.apply(this._node, arguments);
}
});
建立 Editor.Server 類
這個類可以建立一個 ht.Node 節點,並設定節點的顏色和前面貼圖:
var S = E.Server = function(obj) { // 伺服器元件
var color = obj.color,
frontImg = obj.frontImg;
var node = this._node = new ht.Node(); // 建立節點
node.s({ // 設定節點的樣式 s 為 setStyle 的縮寫
'all.color': color, // 設定節點六面的顏色
'front.image': frontImg // 設定節點正面的圖片
});
};
這樣我在需要建立伺服器元件的位置直接 new 一個新的伺服器元件物件即可,並且能夠直接呼叫我們上面宣告的 setHost 等函式,很快我們就會用上。
接下來建立 Editor.Cabinet 機櫃類 ,方法跟上面 Editor.Server 類的定義方法差不多:
ht.Default.def('Editor.Cabinet', Object, {
addToDataModel: function(dm) {
dm.add(this._door);
dm.add(this._node);
this._serverList.forEach(function(s) {
s.addToDataModel(dm);
});
},
p3: function() {
this._node.p3.apply(this._node, arguments); // 設定節點的 3d 座標
}
});
建立 Editor.Cabinet 類
這個類相對於前面的 Editor.Server 伺服器元件類要相對複雜一點,這個類中建立了一個櫃身、櫃門以及機櫃內部的伺服器元件:
var C = E.Cabinet = function(obj) {
var color = obj.color,
doorFrontImg = obj.doorFrontImg,
doorBackImg = obj.doorBackImg,
s3 = obj.s3;
var node = this._node = new ht.Node(); // 櫃身
node.s3(s3); // 設定節點的大小為 setSize3d
node.a('cabinet', this); // 自定義 cabinet 屬性
node.s({ // 設定節點的樣式為 setStyle
'all.color': color, // 設定節點六面的顏色
'front.visible': false // 設定節點前面是否可見
});
if (Math.random() > 0.5) {
node.addStyleIcon('alarm', { // 向節點上新增 icon 圖示
names: ['icon 溫度計'], // 包含多個字串的陣列,每個字串對應一張圖片或向量(通過 ht.Default.setImage 註冊)
face: 'top', // 預設值為 front,圖示在 3D 下的朝向,可取值 left|right|top|bottom|front|back|center
position: 17, // 指定 icons 的位置
autorotate: 'y', // 預設值為 false,圖示在 3D 下是否自動朝向眼睛的方向
t3: [0, 16, 0], // 預設值為 undefined,圖示在 3D 下的偏移,格式為[x,y,z]
width: 37, // 指定每個 icon 的寬度,預設根據註冊圖片時的寬度
height: 32, // 指定每個 icon 的高度,預設根據註冊圖片時的高度
textureScale: 4, // 預設值為 2,該值代表記憶體實際生成貼圖的倍數,不宜設定過大否則影響效能
visible: { func: function() { return !!E.alarmVisible; }} // 表示該組圖片是否顯示
});
}
var door = this._door = new ht.DoorWindow(); // 櫃門
door.setWidth(s3[0]); // 置圖元在 3D 拓撲中的 x 軸方向的長度
door.setHeight(1); // 設定圖元在 3D 拓撲中的 z 軸長度
door.setTall(s3[1]); // 控制 Node 圖元在 y 軸的長度
door.setElevation(0); // 設定圖元中心在 3D 座標系中的y座標
door.setY(s3[2] * 0.5); // 設定節點在 y 軸的位置
door.setHost(node); // 設定吸附
door.s({ // 設定節點樣式 setStyle
'all.color': color, // 設定節點六面顏色
'front.image': doorFrontImg, // 設定節點正面圖片
'front.transparent': true, // 設定節點正面是否透明
'back.image': doorBackImg, // 設定節點背面的圖片
'back.uv': [1,0, 1,1, 0,1, 0,0], // 自定義節點後面 uv 貼圖,為空採用預設值 [0,0, 0,1, 1,1, 1,0]
'dw.axis': 'right' // 設定 DoorWindow 圖元展開和關閉操作的旋轉軸,可取值 left|right|top|bottom|v|h
});
var serverList = this._serverList = [];
var max = 6,
list = E.randomList(max, Math.floor(Math.random() * (max - 2)) + 2); // global.js 中宣告的獲取隨機數的函式
var server, h = s3[0] / 4;
list.forEach(function(r) {
var server = new E.Server({ // 伺服器元件
color: 'rgb(51,49,49)',
frontImg: '伺服器 元件精細'
});
server.s3(s3[0] - 2, h, s3[2] - 4); // 設定節點大小
server.setElevation((r - max * 0.5) * (h + 2)); // 設定節點中心點在 y 軸的座標
server.setHost(node); // 設定節點的吸附
serverList.push(server); // 向 serverList 中新增 server 節點
});
};
上面程式碼中唯一沒提到的是 Editor.randomList 函式,這個函式是在 global.js 檔案中宣告的,宣告如下:
var E = window.Editor = {
leftWidth: 0,
topHeight: 40,
randomList: function(max, size) {
var list = [], ran;
while (list.length < size) {
ran = Math.floor(Math.random() * max);
if (list.indexOf(ran) >= 0)
continue;
list.push(ran);
}
return list;
}
};
好了,場景中的各個部分的類都建立完成,那我們就該將場景建立起來,然後將這些圖元都堆進去!
介面設計
場景建立
如果熟悉的同學應該知道,用 HT 建立一個 3D 場景只需要 new 一個 3D 元件,再將通過 addToDOM 函式將這個場景新增進 body 中即可:
var g3d = E.main = new ht.graph3d.Graph3dView(); // 3d 場景
main.js 檔案中主要做的是在 3D 場景中一些必要的元素,比如牆面,地板,門,空調以及所有的機櫃的生成和排放位置,還有非常重要的互動部分。
牆體,地板,門,空調和機櫃的建立我就不貼程式碼出來了,有興趣的請自行檢視程式碼,這裡主要說一下雙擊機櫃以及與機櫃有關的任何物體(櫃門,伺服器裝置)則 3D 中 camera 的視線就會移動到雙擊的機櫃的前方某個位置,而且這個移動是非常順滑的,之前技藝不精,導致這個部分想了很久,最後參考了這個 Demo 的實現方法。
為了能夠重複地設定 eye 和 center,將設定這兩個引數對應的內容封裝為 setEye 和 setCenter 方法,setCenter 方法與 setEye 方法類似,這裡不重複贅述:
// 設定眼睛位置
var setEye = function(eye, finish) {
if (!eye) return;
var e = g3d.getEye().slice(0), // 獲取當前 eye 的值
dx = eye[0] - e[0],
dy = eye[1] - e[1],
dz = eye[2] - e[2];
// 啟動 500 毫秒的動畫過度
ht.Default.startAnim({
duration: 500,
easing: easing, // 動畫緩動函式
finishFunc: finish || function() {}, // 動畫結束後呼叫的函式
action: function(v, t) { // 設定動畫 v 代表通過 easing(t) 函式運算後的值,t 代表當前動畫進行的進度[0~1],一般屬性變化根據 v 引數進行
g3d.setEye([ //設定 3D 場景中的 eye 眼睛的值,為一個陣列,分別對應 x,y,z 軸的值
e[0] + dx * v,
e[1] + dy * v,
e[2] + dz * v
]);
}
});
};
我沒有重複宣告 setCenter 函式不代表這個函式不重要,恰恰相反,這個函式在“視線”移動的過程中起到了決定性的作用,上面的 setEye 函式相當於我想走到我的目標位置的前面(至少我定義的時候是這種用途),而 setCenter 的定義則是將我的視線移到了目標的位置(比如我可以站在我現在的位置看我右後方的物體,也可以走到我右後方去,站在那個物體前面看它),這點非常重要,請大家好好品味一下。
雙擊事件倒是簡單,只要監聽 HT 封裝好的事件,判斷事件型別,並作出相應的動作即可:
g3d.mi(function(e) { // addInteractorListener 事件監聽函式
if (e.kind !== 'doubleClickData') // 判斷事件型別為雙擊節點
return;
var data = e.data, p3;
if (data.a('cabinet')) // 機身
p3 = data.p3();
else {
host = data.getHost(); // 獲取點選節點的吸附物件
if (host && host.a('cabinet')) { // 如果吸附物件為 cabinet
p3 = host.p3();
}
}
if (!p3) return;
setCenter(p3); // 設定 center 目標的要移向位置為 cabinet 的位置
setEye([p3[0], 211, p3[2] + 247]); // 設定 eye 眼睛要移向的位置
});
頂部導航欄
一開始看到這個例子的時候我在想,這人好厲害,我用 HT 這麼久,用 HT 的 ht.widget.Toolbar 還沒能做出這麼漂亮的效果,看著看著發現這原來是用 form 表單做的,厲害厲害,我真是太愚鈍了。
var form = E.top = new ht.widget.FormPane(); // 頂部 表單元件
form.setRowHeight(E.topHeight); // 設定行高
form.setVGap(-E.topHeight); // 設定表單元件水平間距 設定為行高的負值則可以使多行處於同一行
form.setVPadding(0); // 設定表單頂部和頂部與元件內容的間距
form.addRow([null, { // 向表單中新增一行元件,第一個引數為元素陣列,元素可為字串、json 格式描述的元件引數資訊、html 元素或者為 null
image: {
icon: './symbols/inputBG.json',
stretch: 'centerUniform'
}
}], [40, 260]); // 第二個引數為每個元素寬度資訊陣列,寬度值大於1代表固定絕對值,小於等於1代表相對值,也可為80+0.3的組合
form.addRow([null, null, {
id: 'searchInput',
textField: {}
}, {
element: '機房視覺化管理系統',
color: 'white',
font: '18px arial, sans-serif'
}, null, {
button: {
// label: '檢視切換',
icon: './symbols/viewChange.json',
background: null,
selectBackground: 'rgb(128,128,128)',
borderColor: 'rgba(0, 0, 0, 0)',
onClicked: function() {
E.focusTo();
}
}
}, null, {
button: {
// label: '告警',
icon: './symbols/alarm.json',
togglable: true,
selected: false,
background: null,
selectBackground: 'rgb(128,128,128)',
borderColor: 'rgba(0, 0, 0, 0)',
onClicked: function(e) {
E.setAlarmVisible(this.isSelected());
}
}
}, null], [40, 42, 218, 300, 0.1, 50, 10, 50, 10]);
以上都只是能實現,但是並沒有真正地新增進 html 標籤中,也就意味著,現在介面上什麼都沒有!別忘了在頁面載入的時候將 3D 場景新增進 body 中,同時也別忘了將 form 表單新增進 body 中,並且設定視窗大小變化事件時,form 表單也需要實時更新:
window.addEventListener('load', function() {
g3d.addToDOM(); // 將 3D 場景新增進 body 中
document.body.appendChild(E.top.getView()); //將 form 表單元件底層 div 新增進 body 中
window.addEventListener('resize', function() { // 視窗大小變化事件監聽
E.top.iv(); // 更新 form 表單的底層 div
});
});
這裡說明一下 addToDOM 函式,對於瞭解 HT 的機制非常重要。HT 的元件一般都會嵌入 BorderPane、SplitView 和 TabView 等容器中使用,而最外層的 HT 元件則需要使用者手工將 getView() 返回的底層 div 元素新增到頁面的 DOM 元素中,這裡需要注意的是,當父容器大小變化時,如果父容器是 BorderPane 和 SplitView 等這些 HT 預定義的容器元件,則 HT 的容器會自動遞迴呼叫孩子元件invalidate 函式通知更新。但如果父容器是原生的 html 元素, 則 HT 元件無法獲知需要更新,因此最外層的 HT 元件一般需要監聽 window 的視窗大小變化事件,呼叫最外層元件 invalidate 函式進行更新。
為了最外層元件載入填充滿視窗的方便性,HT 的所有元件都有 addToDOM 函式,其實現邏輯如下,其中 iv 是 invalidate 的簡寫:
addToDOM = function(){
var self = this,
view = self.getView(),
style = view.style;
document.body.appendChild(view); // 將場景的底層 div 新增進 body 中
style.left = '0'; // HT 預設將所有的元件底層 div 的 position 設定為 absolute
style.right = '0';
style.top = '0';
style.bottom = '0';
window.addEventListener('resize', function () { self.iv(); }, false); // 視窗大小變化監聽事件,通知元件變化更新
}
這樣,所有的程式碼就結束了,可以自己右鍵“檢查”,network 中可以獲取相對應的 json 檔案。
相關文章
- 基於 HTML5 WebGL 的 3D 機房HTMLWeb3D
- 基於 HTML5 WebGL 的 3D 風機視覺化系統HTMLWeb3D視覺化
- 基於 HTML5 WebGL 的挖掘機 3D 視覺化應用HTMLWeb3D視覺化
- 基於 HTML5 WebGL 的發動機 3D 視覺化系統HTMLWeb3D視覺化
- 基於 HTML5 的 3D 機房視覺化實景監控HTML3D視覺化
- 基於 HTML5 + WebGL 的無人機 3D 視覺化系統HTMLWeb無人機3D視覺化
- 基於 WebGL 的 HTML5 3D 工控隧道視覺化系統WebHTML3D視覺化
- 基於 HTML5 WebGL 的加油站 3D 視覺化監控HTMLWeb3D視覺化
- 基於 HTML5 + WebGL 的太陽系 3D 視覺化系統HTMLWeb3D視覺化
- 基於 WebGL 的 HTML5 樓宇自控 3D 視覺化監控WebHTML3D視覺化
- 基於 HTML5 的 WebGL 3D 地鐵站視覺化系統HTMLWeb3D視覺化
- 基於 HTML5 的 WebGL 樓宇自控 3D 視覺化監控HTMLWeb3D視覺化
- 基於 HTML5 WebGL 的地鐵站 3D 視覺化系統HTMLWeb3D視覺化
- 基於 HTML5 和 VR 技術的 3D 機房資料中心視覺化HTMLVR3D視覺化
- 基於 WebGL 3D 的 HTML5檔案館視覺化管理系統Web3DHTML視覺化
- 基於 HTML5 的 WebGL 3D 檔案館視覺化管理系統HTMLWeb3D視覺化
- 基於HTML5的WebGL呈現A星演算法的3D視覺化HTMLWeb演算法3D視覺化
- WebGL入門之基於WebGL的3D視覺化引擎介紹Web3D視覺化
- 基於 HTML5 + WebGL 的 3D 風力發電場視覺化系統HTMLWeb3D視覺化
- 基於 HTML5 WebGL 的 3D 挖掘機HTMLWeb3D
- 基於HTML5的WebGL經典3D虛擬機器房漫遊動畫HTMLWeb3D虛擬機動畫
- 基於HTML5的WebGL電信網管3D機房監控應用HTMLWeb3D
- 基於 HTML5 WebGL + WebVR 的 3D 虛實現實視覺化培訓系統HTMLWebVR3D視覺化
- 基於 HTML5 WebGL 的 3D 科幻風機HTMLWeb3D
- 基於 HTML5 Canvas 的 3D 機房建立HTMLCanvas3D
- 基於 HTML5 WebGL 與 GIS 的智慧機場大資料視覺化分析HTMLWeb大資料視覺化
- 基於 HTML5 的 WebGL 3D 隧道監控HTMLWeb3D
- 基於HTML5 WebGL的工業化3D電子圍欄HTMLWeb3D
- 視覺化學習:WebGL的基礎使用視覺化Web
- 基於HTML5快速搭建3D機房裝置皮膚HTML3D
- 基於 WebGL 的 HTML5 3D 工控裙房系統WebHTML3D
- 基於 HTML5 WebGL 的 3D 模型斜面生成HTMLWeb3D模型
- 基於 WebGL HTML5 的 3D 模型分離控制WebHTML3D模型
- 基於 HTML5 WebGL 的 3D 工控裙房系統HTMLWeb3D
- 基於 HTML5 WebGL 的 3D “彈力”佈局HTMLWeb3D
- 基於 HTML5 的工業網際網路 3D 視覺化應用HTML3D視覺化
- 基於 HTML5 WebGL 構建智慧數字化城市 3D 全景HTMLWeb3D
- 基於WebGL架構的3D視覺化平臺—三維裝置管理(ThingJS實現樓宇裝置管理3D視覺化)Web架構3D視覺化JS