



// 控制是否載入 obj 的四邊面,否的話通過算法合並三角面 ht.Style['wf.loadQuadWireframe'] = false; // 控制是否顯示四邊面 ht.Style['wf.combineTriangle'] = true;
在風格基調確定后,在主體大樓場景做還需要做一些簡單的事件機制處理,例如模型選中狀態的表現和設備預警信息彈窗的顯示。
模型狀態的體現是開啟了模型選中的外框高亮顯示:
// 開啟模型選中高亮線框寬度為1 g3d.getHighlightHelper().mode = 1;
開啟了模型選中高亮后,我們可以很清晰地體現出所點擊的模型,搭配上點擊事件的處理,設備信息彈窗的展示,在交互體驗上就會有一種很友好的效果展示。對於設備信息的彈窗展示,是先通過對設備進行綁定標簽,然后通過這個唯一的標簽在數據模型 dataModel 去找到這個設備,然后彈出相對應的彈窗信息或者預警事件。
// 根據唯一標識標簽從數據模型中獲取節點信息 this.equipmentPanel = g3dDm.getDataByTag('equipmentPanel'); this.alarmEquipmentPanel = g3dDm.getDataByTag('alarmEquipmentPanel'); this.buildingPanel = g3dDm.getDataByTag('buildingPanel'); handleInteractive(e) { // 獲取事件類型 kind 和事件處理節點 data const {kind, data} = e; if (kind === 'clickData') { let tag = data.getTag(); if (!tag) return; if (tag === 'equipment') { // 獲取所點擊設備的位置信息 var p3d = data.getPosition3d(); // 設備位置信息上對應空間坐標 Y 軸上設定增加20的高度 p3d[1] = p3d[1] + 20; // 獲取設備面板 var panel = this.equipmentPanel; // 設備面板顯示展示 panel.s('3d.visible',true); // 設置設備面板坐標 panel.setPosition3d(p3d); // 隱藏大樓面板和預警面板 this.buildingPanel.s('3d.visible',false); this.alarmPlane.s('3d.visible',false); } if (tag === 'alarmEquipment') { // 獲取所點擊設備的位置信息 var p3d = data.getPosition3d(); // 設備位置信息上對應空間坐標 Y 軸上設定增加20的高度 p3d[1] = p3d[1] + 20; // 獲取預警面板 var panel = this.alarmEquipmentPanel; // 預警面板顯示展示 panel.s('3d.visible',true); // 設置預警面板坐標 panel.setPosition3d(p3d); // 隱藏大樓面板和設備面板 this.buildingPanel.s('3d.visible',false); this.equipmentPanel.s('3d.visible',false); } if(tag === 'building'){ // 顯示大樓面板 this.equipmentPanel.s('3d.visible',true); // 隱藏設備面板 this.alarmEquipmentPanel.s('3d.visible',false); // 隱藏預警面板 this.buildingPanel.s('3d.visible',false); } } // 點擊背景則隱藏所有面板信息 if(kind === 'clickBackground'){ this.equipmentPanel.s('3d.visible',false); this.alarmEquipmentPanel.s('3d.visible',false); this.buildingPanel.s('3d.visible',false); } }
// 遍歷數據模型獲取所要尋找的標識節點做相應的動畫 g3dDm.each((data) => { // 獲取節點標識 let tag = data.getTag(); if (tag === 'num') { // 數字飛升動畫 animNum(data); } else if (tag === 'car') { // 設置車輛節點的初始 uv 偏移 data.s('top.uv.offset', [1, 0]); // 車輛穿梭動畫 animCar(data); } else if (tag === 'light') { //光柱飛升動畫 animLight(data); } });
而所有動畫效果的實現,都是基於 HT 封裝的 ht.Default.startAnim() 動畫函數,支持 Frame-Based 和 Time-Based 兩種方式的動畫,本可視化系統中采取的是后面一種實現方式,通過 duration 對於動畫時間的控制和 easing 讓用戶自定義,通過數學公式控制動畫,如勻速變化,先慢后快等效果。基於動畫函數的實現上,對各自展示節點的效果表現上,又封裝了三個函數做對應的處理。
數字飛升動畫效果實現的封裝函數為:
function animNum(data) { // 設置節點大小的范圍隨機數處理 var temp3 = 16 - 8 * (Math.random()); // 設置動畫運行時間的范圍隨機數處理 var temp4 = 1200 + Math.random() * 2000; // 設置節點在空間坐標 Y 軸上的范圍隨機高度 var temp5 = 400 + Math.random() * 200; // 開啟動畫函數 ht.Default.startAnim({ duration: temp4, easing: function (t) { return t * t }, action: function (v, t) { // 獲取節點的位置坐標信息 var p3d = data.getPosition3d(); // 設置節點的新位置坐標信息 data.setPosition3d(p3d[0], temp5 - temp5 * v, p3d[2]); // 設置節點的大小信息 data.setSize3d(temp3, temp3, temp3); }, // 動畫函數結束后繼續回調此動畫函數 finishFunc: function () { animNum(data); } }); }
車輛穿梭動畫效果實現的封裝函數為:
function animCar(data) { // 開啟動畫函數 ht.Default.startAnim({ duration: 5000, easing: function (t) { return t }, action: function (v, t) { // 判斷節點的頂面貼圖是否為所需的對應信息貼圖 if (data.s('top.image') === 'symbols/htdesign/填充/飛光漸變 2.png') { // 獲取節點的 uv 偏移信息 var offsetX = data.s('top.uv.offset')[0]; // 設置偏移新值到節點上 offsetX = (offsetX - 0.01) % 1; data.s('top.uv.offset', [offsetX, 0]); } }, // 動畫函數結束后繼續回調此動畫函數 finishFunc: function () { animCar(data); } }); }
而光柱的實現方式上也是與數字飛升的效果一樣,通過在隨機的范圍位置坐標內通過設定不同的時間差隨機生成,來形成與數字飛升為對立面的光柱下降效果,與線框建築的科技風格融為一體,很好地詮釋了整體風格的展示,這里對於光柱的動畫就不再多加贅述了。
相對應的是,停車場隨機停放的效果展示,不同於以上的動畫視覺展示,本身還是具有其效果意義的,可以對接真實的數據進行對整個停車場的車輛安放做可視化的數據維護和管理,而我們這里的實現上,則很好地模擬了這一事件的處理方式,也是通過一個簡單的封裝函數來體現停車場的動畫效果:
function animPark(data) { // 設置隨機值來體現車輛隨機停放的信息 var temp = Math.random(); // 根據隨機值判斷車輛安放的狀態 if (temp<0.15) { data.s('all.color','rgb(255,184,77)'); } else if (temp>0.6) { data.s('all.color','rgba(0,153,255,0.10)'); } ht.Default.startAnim({ duration: 2000, easing: function (t) { return t }, action: function (v, t) { }, // 動畫函數結束后繼續回調此動畫函數 finishFunc: function () { animPark(data); } }); }

- direction:默認undefined,眼睛處於目標的方向(相對目標,受到目標自身旋轉影響),例如[0,1,5]在目標正面的斜向上;
- animation:默認false,是否使用動畫,可以設置為true或者false或者animation動畫對象;
- ratio:默認0.8,浮點類型,表示眼睛跟中心的距離動態計算(例如 0.8 表示眼睛在上述方向上動態計算距離以將目標包圍盒的8個角全部適配到屏幕80%范圍內);
g3d.flyTo(data, { direction: [0, 10, 10], animation: true, ratio: 0.9, });
對於門的開啟動畫,首先是將門設置對應的機櫃為父節點,通過點擊事件的監聽處理后,根據多點擊的節點,將對應的門節點和旋轉角度信息,去調用門的封裝動畫函數:
// 傳入節點和旋轉角度信息 export function animDoor(data, x) { // 開啟動畫函數 ht.Default.startAnim({ duration: 1200, easing: function (t) { return t }, // 動畫執行函數,根據傳入的角度信息做旋轉角度的動畫 action: function (v, t) { data.setRotation3d(0,-v * x,0); }, finishFunc: function () { // 設置門的父節點機櫃透明度為0.1 data.getParent().s('shape3d.opacity', 0.1); // 遍歷門的父節點機櫃並設置透明度為0.1 data.getParent().eachChild(function (data) { data.s('shape3d.opacity', 0.1); }) } }); }
對於雙擊背景的視角返回處理,是通過 HT 封裝的相機移動函數 moveCamera(),可以根據所要到達的視角中心(center)和眼睛(eye),通過開啟動畫函數達到一種視角切換的過渡效果:
- eye:相機位置坐標;
- center:中心點位置坐標;
- anim:默認 false,是否使用動畫,可以設置為true或者false或者animation動畫對象;
g3d.moveCamera([1294, 898, 1671], [0, 0, 0], true);
對於機櫃所占用的能耗和處理能力,可以通過機櫃利用率來體現,這樣不僅能直觀地體現每一個機櫃的使用情況,還能通過反饋的使用情況,即時對一些負載的機櫃或者是低使用率的機櫃,做出智能調整,使其機櫃群達到最大效率化的工作狀態。而具體的實現方法是通過在機櫃群上動態生成,占用機櫃高度比例大小的節點,通過隨機取值的方式,並且約定能耗顏色的顯示,來體現出機櫃當前的利用率信息。
loadCapacityNode(g3dDm, cabinetList) { cabinetList.forEach((data) => { // 創建新的利用率容量節點 var node = new ht.Node(); // 生成隨機數 var randomNumber = Math.random() * 100; // 通過隨機數值來體現對應的機櫃利用率顏色的變化 var color; if (randomNumber <= 30) { color = 'rgb(51,153,255)'; } else if (randomNumber > 30 && randomNumber < 60) { color = 'rgb(240,225,19)'; } else { color = 'rgb(242,83,75)'; } // 設置利用率容量節點的位置信息 node.p3(data.p3()); // 設置利用率容量節點的錨點信息 node.setAnchor3d(data.getAnchor3d()); // 設置利用率容量節點的高度信息 node.s3(data.getWidth(), data.getTall() * (randomNumber/100), data.getHeight()); // 設置利用率容量節點的一些基本屬性 node.s({ '3d.visible': false, '3d.movable': false, 'all.color': color, 'wf.visible': true, 'wf.color': 'rgb(247,247,247)' }); // 設置容量節點為機櫃,方便返回房間視角的時候遍歷機櫃節點一並隱藏 g3dDm.add(node); this.capacityList.push(node); }) }
