基於 HTML5 WebGL 的樓宇智慧化集成系統(一)

前言
      隨著現代通訊技術、電腦技術、控制技術的飛速發展,智慧建築已經成為現代建築發展的主流。智慧建築是以建築物為平台,兼備資訊設施系統、資訊化應用系統、建築設備管理系統、公共安全系統等。集結構、系統、服務、管理及其優化組合為一體,向人們提供一個安全、高效、隨身、節能、環保、健康的建築環境。
 
      IBMS(Intelligent Building Management System),即智慧化集成系統,是指在 BAS 的基礎上更進一步的與通訊網路系統、資訊網路系統實現更高一層的建築集成管理系統。在資訊化時代的今天,諸多建築與集成的管理系統融合,生成了一套高效的智慧建築集成解決方案。IBMS 更多突出的是管理方面的功能,即如何的全面實現優化控制和管理,節能降耗、高效、舒適、環境安全這樣一個目的,可以這樣說,判斷一個建築物是否具有智慧建築特點,要看它是否具有 IBMS 的系統集成。這是很重要的判定條件。另一個重要的前提是,在做好這項工程的同時不要忽視了同步建設的資訊化工程。一個成功的 IBMS 系統集成會在諸多的管理方面能發揮其顯著的經濟優勢。
 
      傳統的 智慧樓宇/樓宇自動化/樓宇安防/智慧園區 常會採用 BIM(建築資訊模型 Building information modeling)軟體,如 Autodesk 的 Revit 或 Bentley 這類建築和工程軟體,但這些 BIM 建模模型的數據往往過於龐大臃腫,絕大部分細節資訊對樓宇自控意義不大,反而影響拖累了行業 Web SCADA 或 Web 組態監控的趨勢,所以我們採用以 Hightopo 的 HT for Web 產品輕量化 HTML5/WebGL 建模的方案,實現快速建模、運行時輕量化到甚至手機終端瀏覽器即可 3D 可視化運維的良好效果。
 
      本系列文章為了幫助用戶更直觀友好的瀏覽當前的樓宇智控系統,分成了三個小節來介紹場景以及效果實現的運用:
1、冷站熱站中央空調末端智慧群控系統 以及 3D 動畫效果以及切換漫遊;
2、面板組件動畫效果和 樓層監控系統 影片的引入;
3、智慧樓宇管理系統電梯監控系統 以及 停車場管理系統
 
介面簡介及效果預覽
介面初始化及漫遊效果
      場景在載入的時候會讀取模型資訊,確認模型載入完畢後才開始執行動畫效果,然後通過場景的漫遊效果,可以直觀地去巡視整座大樓建築的場景資訊。
 
 
冷站場景效果:優化冷水機組效率,按需供給
      在智慧樓宇的中央空調冷熱源系統中,冷站場景分有冷卻水系統、冷水機組以及冷凍水系統所組成。冷卻水系統的作用是為冷水機組的冷凝器提供冷卻水,吸收製冷劑的冷凝熱量,並將冷凝熱量轉移到大氣中去。冷凍水系統的作用是為冷水機組的蒸發器提供的冷量通過冷凍水輸送到各類冷水用戶。
 
 
熱站場景效果:優化熱泵機組效率,按需供給
      在智慧樓宇的中央空調冷熱源系統中,熱站場景分有冷卻機組系統和熱水泵系統所組成。通過冷卻機組系統的換熱器不斷加熱了中央空調系統內的空調水,並通過熱水循環中的熱水泵系統進行循環給用戶提供熱量。
 
中央空調末端智慧群控系統場景效果:靈活對應多樣智慧調節空間
      在智慧樓宇的中央空調冷熱源系統中,末端智慧節能控制系統,通過室內溫濕度可以進行模組內部的調節送風溫度,水閥開度及風機頻率,在保證末端舒適度的前提下,使供冷量與需求相匹配,最大限度地降低風機能耗。
 
智慧樓宇管理系統優化效果
      主要包括冷站、熱站和中央末端智慧群控的聯合作用,以及樓層智慧照明,通過清晰的動畫體現出整棟大樓智慧節能運作的流程,可以通過面板詳情的演示細緻地介紹每個場景的作用以及串聯的用處。
 
電梯以及樓層監控效果
      可視化地實時監控電梯在樓層間的工作運行狀態,並且能夠準確地瀏覽每個電梯內的實時監控畫面。
 
 
停車場管理系統監控效果
      停車場作為現在樓宇監控不可缺失的一環,這裡主要可以體現出實時的車位監控,通過簡單的動畫演示來表現出整個停車場車輛的運行狀態,方便管理。
 
程式碼實現
一、場景搭建
      介面通過 2D 圖紙疊加在3D 場景上來實現 2D 介面 與 3D 場景的融合,2D 介面通過自動布局的機制實現了手機端與電腦端的響應式呈現。
      通過 2D 視圖的組件 ht.graph.GraphView 和 3D 視圖的組件 ht.graph3d.Graph3dView 創建出呈現 2D 視圖的組件類 g2d 以及呈現 3D 視圖的組件類 g3d,在分別獲取各自的數據模型 DataModel,來對圖紙場景做一些數據可視化的操作,這裡值得一提的是,我對於 2D 介面和 3D 場景的融合,是通過把 getView() 獲取到 g3d 拓撲組件的根層 div,然後 addToDOM() 將 g2d 組件加入到指定的 DOM 元素底下。
      可以通過<HT的入門手冊>了解到更多視圖與數據模型之間的內容。
// 創建二維拓撲視圖  this.g2d = new ht.graph.GraphView();  this.g2dDm = this.g2d.dm();    // 創建三維拓撲視圖  this.g3d = new ht.graph3d.Graph3dView();  this.g3dDm = this.g3d.dm();    // 將二維圖紙嵌入到三維場景中  this.g2d.addToDOM(this.g3d.getView());    // 修改左右鍵交互方式  let mapInteractor = new ht.graph3d.MapInteractor(this.g3d);  this.g3d.setInteractors([mapInteractor]);    // 修改最大仰角為 PI / 2  mapInteractor.maxPhi = Math.PI / 2;    const G = {};  window.G = G;  // 事件派發  G.event = new ht.Notifier();

 

 

3D 場景載入主視圖為:

 

      首先我搭建了一個 3D 的場景用來放置我們的 json 場景數據,利用 ht.Default.xhrLoad 函數解析 json 場景數據,並通過 deserialize 將反序列化的對象加入DataModel來顯示載入 3D 場景,有興趣的可以通過<HT的序列化手冊>來了解這一機制的實現。

ht.Default.xhrLoad('scenes/demo.json', (json) => {      if (!json) return;      g3dDm.deserialize(json);        // 設置三維視圖的中心點和相機位置      g3d.setCenter([-342, -64, 389]);      g3d.setEye([-355, 10833, 2642]);        // 設置最遠距離      g3d.setFar(1000000);      // 獲取球圖標,設置為天空球      let skybox = g3dDm.getDataByTag('skyBox');      g3d.setSkyBox(skybox);        // 模型載入完後執行動畫      const modelList = [];      g3dDm.each(d => {          const shape3d = d.s('shape3d');          if (!shape3d || !shape3d.endsWith('.json')) return;          if (ht.Default.getShape3dModel(shape3d)) return;          modelList.push(shape3d);      });      ht.Default.handleModelLoaded = (name, model) => {          const index = modelList.indexOf(name);          if (index < 0) return;          modelList.splice(index, 1);          if (modelList.length > 91) return;          ht.Default.handleModelLoaded = () => {          };            // 模型載入完侯,默認執行場景切換動畫          g3d.moveCamera([257, 713, 1485], [7, 40, 144], {              duration: 2000,              finishFunc: () => {                  this.load2D();              }          });      };  });

 

2D 面板載入視圖為:

 

      同樣,我搭建了一個 2D 的場景用來放置我們的 json 矢量圖,利用 ht.Default.xhrLoad 函數將 json 矢量背景圖反序列化顯示在 2D 面板數據。

ht.Default.xhrLoad('displays/demo.json', (json) => {      if (!json) return;      g2dDm.deserialize(json);        // 面板動畫入口      this.tittleAnim();      this.panelTime();        // 2D圖紙載入完後執行事件處理      this.loaded2DHandler();  });

 

 

二、3D 動畫效果以及切換漫遊

      對於 3D 建模下的樓宇建築,加上場景的全方位漫遊,可使用戶達到一種沉浸式的體驗,更加直觀地去感受這個樓宇下各個場景的聯繫,依次地介紹了冷站、智慧末端以及熱站的位置以及功能運作的動畫 。主要運用的方法是通過藉助 HT 提供的 ht.Shape 圖元類型,可以在 GraphView 和 Graph3dView 組件上展示出各種二維和三維的形狀效果,而漫遊的管道路線就是由其擴展子類 ht.Polyline 去繪製實現一條三維的管道,然後用這條繪製的管道加上漫遊的時間去調用這個漫遊的方法,其本質上是圍繞著中心點,然後根據管道去不斷地改變視角下的 eye 和 center 的數值,達到環視這個建築的整體視角。

      這裡可以了解一下關於空間軌道的繪製,詳見<HT的形狀手冊>的空間管線章節。

      以下是環視漫遊動畫的偽程式碼:

polyLineRoam(polyLine, time) {      const g3d = this.g3d;      const g3dDm = this.g3dDm;      this.roamButton.a('active', true);      this.roamAnim = ht.Default.startAnim({          duration: time,          easing: t => t,          action: (v, t) => {              let length = this.main.g3d.getLineLength(polyLine),              offset = this.main.g3d.getLineOffset(polyLine, length * v),              point = offset.point,              px = point.x,              py = point.y,              pz = point.z;                g3d.setEye(px, py, pz);              g3d.setCenter(7, 40, 144);          },          finishFunc: () => {              this.roam1();          }      });  }

 

      在整體建築的環視漫遊完後,我們可以通過拉近各個場景的視角,來依次巡視各個場景所執行的動畫。在根據管道改變 eye 和 center 環視漫遊方法結束後,用動畫的結束回調 finishFunc 去調用下一個動畫的執行,而巡視漫遊就在這裡去調用,以下我們以巡視冷站的漫遊動畫為例去介紹實現的方法。

      巡視漫遊的主要實現方法是通過 HT 核心包的相機移動 moveCamera 來實現的, 通過參數 (eye, center, animation) 來調用這個方法:

  • eye:新的相機位置,形如[-291, -8, 283],如果為 null 則使用當前相機的位置;
  • center:新的目標中心點位置(相機看向的位置),形如[148, -400, 171],如果為 null 則使用當前中心點位置;
  • animation:默認 false,是否啟用動畫,可以設置為 true 或者 flase 或者 animation 動畫對象;

      每次執行完一個場景的視角移動後,再通過相機移動動畫的結束回調 finishFunc 調用下一個相機移動的動畫,達到巡視漫遊的效果。

// 切換到冷站視角  roam1() {      const g3d = this.g3d;      const g3dDm = this.g3dDm;      this.roamAnim = g3d.moveCamera([-291, -8, 283], [148, -400, 171], {          duration: 500,          easing: t => t * t,          finishFunc: () => {              this.roam2();          }      });  }

 

      在環視漫遊和巡視漫遊的執行下,我們也可以觸發 2D 圖紙右面板下的按鈕面板去觀看我們想要瀏覽的指定場景,這時候就會關閉當前在執行的環視漫遊或者巡視漫遊,再次點擊改按鈕則返回場景的主視角,或者點擊左上角漫遊按鈕又可以進入環視漫遊,這樣的交互體驗,可以方便用戶即使地查看想要瀏覽的場景,而不用依靠等待逐一漫遊下去查看,也不會干擾到漫遊的整體體驗。相應地通過介紹冷站按鈕的點擊觸發介紹一下實現的方法。

      一般的交互方式存在三種事件交互的方法,包括事件通知管理器 ht.Notifier 類,內置的 Interator 在交互過程會派發出事件和數據綁定的監聽來實現,而這裡使用的是第三種交互方式。

      通過數據綁定監聽到 onDown 執行按下的事件後,通過改變按下和再次按下的按鈕狀態 active 來分別執行相機移動去切換視角,主要實現的偽程式碼如下:

// 設置圖元可交互  this.coolingCentralStationButton.s('interactive', true);  // 通過數據綁定監聽到onDown執行按下的事件  this.coolingCentralStationButton.s('onDown', () => {  // 切換到冷站時,2d面板所執行的切換動畫  this.switchToColdStation();  // 按鈕初始化  this.buttonTearDown();  // 按鈕按下效果的狀態      let active = this.coolingCentralStationButton.a('active');      // button為按鈕集合數組,當按下電梯按鈕,其他按鈕默認false      button.forEach(btn => {          btn.a('active', false);      });      // 冷站按鈕的狀態切換      this.coolingCentralStationButton.a('active', !active);      // 根據冷站按鈕的狀態執行切換到冷站或者切換回主視角      if (active) {          // 相機移動切換到主視角          moveCamera(g3d, [257, 713, 1485], [7, 40, 144], {              duration: 2000,              easing: t => t * t          });      } else {          // 漫遊動畫對象如果不為空,則暫停漫遊動畫對象並且設置為空          if (this.roamAnim !== null) {              this.roamAnim.pause();              this.roamAnim = null;          }          // 相機移動切換到冷站視角          coolingCentralStationAnimation = moveCamera(g3d, [-291, -8, 283], [148, -400, 171], {              duration: 2000,              easing: t => t * t          });      }  });

 

      當然,在 3D 場景下還有一些很有趣的動畫效果,比如車流效果、飛光效果和圓環擴散效果。車流效果主要通過採用了貼圖的 uv 的偏移來實現達到車流穿梭的科技感效果;而飛光效果則是採用調度動畫的方法來間隔設置飛光的高度,達到最高點則消失然後重新輪迴動畫展示;圓環擴散效果則是同樣採用調度動畫的方法來間隔設置圓環的縮放值和透明度,來達到擴散消失的效果。

      對於間隔的調度動畫,為了實現動畫的流暢性,這裡調度使用的 loop 是運用到自己封裝 HT 的動畫 ht.Default.startAnim 的一個方法:

  • frames 動畫幀數,這裡不鎖定幀數,可以適應本身動畫的幀數;
  • interval 動畫間隔,單位ms,默認設置20ms。
loop(action, interval = 20) {      return ht.Default.startAnim({          frames: Infinity,          interval: interval,          action: action      });  }

 

 

      然後通過調用這個 loop 的間隔動畫方法,我們來實現車流效果、飛光效果和圓環擴散效果,實現的參考偽程式碼如下:

// 車流圖元的初始化  let traffic = g3dDm.getDataByTag('traffic');  // 圓環擴散圖元的初始化  let lightRing = this.lightRing = g3dDm.getDataByTag('lightRing');  // 飛光圖元設置三種透明狀態數組集合flyMap的初始化  [1, 2, 3].forEach(i => {      const data = flyMap['fly' + i] = g3dDm.getDataByTag('fly' + i);      data.eachChild(d => {          d.s({              // 打開透明度              'shape3d.transparent': true,              // 根據不同的數組集合設置不同的透明度              'shape3d.opacity': i === 3 ? 0.5 : 0.7,              // 設置沿著y軸自動旋轉              'shape3d.autorotate': 'y'          });      });  });    if (this.flyAnim) return;  this.flyAnim = loop(() => {      // 飛光根據間隔設置高度來達到上升的效果      for (let k in flyMap) {          const data = flyMap[k];          let e = data.getElevation() + flyDltMap[k];          if (e >= 500) e = -400;          data.setElevation(e);      }        // 車流根據設置間隔增長uv偏移量來實現穿梭的效果      traffic.eachChild(c => {          c.s('all.uv.offset', [location, 0]);      });      location -= 0.03;        // 旋轉震蕩波透明度漸降      let percent = lightRing.a('percent') || 0,          scale = 15 * percent + 0.5;      lightRing.setScale3d([scale + 1, scale, scale + 1]);      lightRing.s('shape3d.opacity', (1 - percent) * 0.5);      percent += 0.01;      if (percent >= 1) {          percent = 0;      }      lightRing.a('percent', percent);  }, 50);

 

三、冷站場景和熱站場景的動畫實現

      場景動畫中機組的風扇、集水器的蓄滿以及水的流動效果:

      動畫的實現主要還是通過 HT 自帶的 ht.Default.startAnim 動畫函數,支援 Frame-Based 和 Time-Based 兩種方式的動畫。同樣的,我們這裡使用的是 Frame-Based 來封裝一個 loop 函數來執行每一幀間隔的動畫。

      一般來說,動畫可通過自行配置來達到自己想要實現的方法,這裡可以了解< HT 的入門手冊>關於動畫函數的介紹。

if (this.stationAnim) return;  this.stationAnim = loop(() => {      // 冷站水管流動      coldFlow_blue.eachChild(c => {          c.s('shape3d.uv.offset', [-location, 0]);      });      coldFlow_yellow.eachChild(c => {          c.s('shape3d.uv.offset', [location, 0]);      });        // 熱站水管流動      heatFlow_blue.eachChild(c => {          c.s('shape3d.uv.offset', [-location, 0]);      });      heatFlow_yellow.eachChild(c => {          c.s('shape3d.uv.offset', [location, 0]);      });        location -= 0.03;        // 冷站風扇旋轉      cold_fan.eachChild(c => {          c.setRotation3d(c.r3()[0], c.r3()[1] + (Math.PI / 10), c.r3()[2]);      });      // 熱站風扇旋轉      heat_fan.eachChild(c => {          c.setRotation3d(c.r3()[0], c.r3()[1] + (Math.PI / 10), c.r3()[2]);      });        // 集水器水位變化      HotWaterTankTall += 0.25;      if (HotWaterTankTall > 15) {          HotWaterTankTall = 0;      }      coldWaterTankTall1 += 0.25;      if (coldWaterTankTall1 > 20) {          coldWaterTankTall1 = 0;      }      coldWaterTankTall2 += 0.25;      if (coldWaterTankTall2 > 20) {          coldWaterTankTall2 = 0;      }      hotWaterTank.setTall(HotWaterTankTall);      coldWaterTank1.setTall(coldWaterTankTall1);      coldWaterTank2.setTall(coldWaterTankTall2);  }, 50);

 

 

四、中央空調末端智慧群控系統場景效果

      這裡採用了模擬數據的方式來體現末端智慧節能控制的效果。應用於真實項目的時候,可以採用數據介面的方式來實時對接真實數據,可以達到實時監控的效果。

      我使用了自己 mock 的末端群控的數據參數,格式如下:

var boxData =      [          [{              // 設備編號              id: 'box1',              // 設備的溫度              temperature: 23.8,              // 設備的頻率              frequency: 45.8          }, ...]          ...      ];

 

 

      這裡的實現也是通過 loop 循環執行數據的讀取,當數組指標 index 讀取到最後一個數據時,立即關閉循環並清空 loop調度。

boxAnimation = loop(() => {          for (let i = 0, l = 16; i <= l-1; i++) {              let roomTag, roomBox, tag;              tag = i+1;              roomTag = 'boxPanel' + tag;              roomBox = 'box' + tag;                let panel = g3dDm.getDataByTag(roomTag);              let box = g3dDm.getDataByTag(roomBox);              if (panel) {                  panel.a('valueT', boxData[index][i].temperature + '℃');                  panel.a('valueK', boxData[index][i].frequency + 'Hz');                  // 手動更新快取的面板資訊                  g3d.invalidateShape3dCachedImage(panel);                  // 根據溫度判斷設備的顏色                  if (box && parseFloat(panel.a('valueT')) < 26) {                      box.s('shape3d.blend', 'rgb(4,67,176)');                      box.s('wf.color', 'rgb(4,67,176)');                  } else if (box && parseFloat(panel.a('valueT')) >= 26 && parseFloat(panel.a('valueT')) <= 28) {                      box.s('shape3d.blend', 'rgb(28,189,87)');                      box.s('wf.color', 'rgb(28,189,87)');                  } else if (box && parseFloat(panel.a('valueT')) > 28) {                      box.s('shape3d.blend', 'rgb(181,43,43)');                      box.s('wf.color', 'rgb(181,43,43)');                  }              }          }          index++;          if (index >= 10) {              boxAnimation.pause();              boxAnimation = null;          }      }, 500);

 

 

總結

      IBMS 智慧化集成系統管理對於建築園區管理的重要性日趨上升,在資訊時代里不僅可以很好地體現出資訊數據管理的明確性,也體現了智慧管理的便利有效性。通過 3D 場景樓宇園區的動畫加上環視漫遊和巡視漫遊的配合,充分體現了 3D 場景的擬真優點,但是如何實現場景動畫的觸發實現呢?這裡當然必不可少了 2D 面板上的交互和動畫,在下期我們會為大家介紹一些 2D 面板的交互和動畫實現,帶您解讀不一樣的 2D/3D 融合。
 
      2019 我們也更新了數百個工業互聯網 2D/3D 可視化案例集,在這裡你能發現許多新奇的實例,也能發掘出不一樣的工業互聯網:https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA
      同時,你也可以查看更多案例及效果:https://www.hightopo.com/demos/index.html