基於 HTML5 Canvas 的 3D 渲染引擎構建機架式服務器

前言

今天找到了 HT 的官網裏的 Demo 網站( http://www.hightopo.com/demos/index.html ),看的我眼花繚亂,應接不暇。 並且 HT 的用戶手冊,將例子和文檔無縫融合一體,小小 10 來兆開發包竟然包含了四十五份手冊,數百個活生生的 HTML5 例子,還沒體驗過的朋友趕忙來看一看,這回可玩嗨了!html

對於 HT 初學者,面對這一堆數百個涵括通用組件、網絡拓撲圖組件、3D 組件、矢量圖形、各類編輯器等等五法八門的 HTML5 例子盛宴,每每無從下手。爲此,老鄭我打算爲像我同樣喜歡這方面的新手朋友多寫幾篇這樣的博客,慢慢的給你們講述各類各樣的愈來愈多的有趣的小功能!node

效果圖

( https://hightopo.com/demo/CabinetAnimat/網絡

代碼實現

HT 提供了基於 WebGL 的 3D 技術的圖形組件 ht.graph3d.Graph3dView,WebGL 基於 OpenGL ES 2.0 圖形接口,所以 WebGL 屬於底層的圖形 API 接口,二次開發仍是有很高的門檻,HT 的 Graph3dView 組件經過對 WebGL 底層技術的封裝,與 HT 其餘組件同樣,基於 HT 統一的 DataModel 數據模型來驅動圖形顯示,極大下降了 3D 圖形技術開發的門檻。同時 HT 提供了強大的徹底基於 HTML5 技術 3D 圖形建模設計器,用戶無需編碼便可快速可視化搭建各類 3D 場景,能夠說 HT 的 3D 開發模式徹底打破了傳統 3D 開發模式,絕大部分應用再也不須要依賴精通 3ds Max 或 Maya 的專業 3D 設計師來建模,也不須要整合 Unity3d 等引擎作圖形渲染,HT 一站式的提供了從建模到渲染,包括和 2D 組件呈現和數據融合的一站式解決方案。編輯器

我本次講解的就是這個 3D 的界面,因此咱們首先要建立 3D 渲染引擎組件,可視化呈現數據模型的三維環境場景。ide

var dm = new ht.DataModel()
var g3d = new ht.graph3d.Graph3dView(dm)

咱們還要設置眼睛(或Camera)所在位置以及中心點(目標)的位置,格式均爲 [x, y, z] 。函數

g3d.setEye([-376, 270, 896])
g3d.setCenter([-16, 118, -186])

這裏給你們說一下,可參考 3D 手冊( http://www.hightopo.com/guide/guide/core/3d/ht-3d-guide.html )。如上圖所示,透視投影最終顯示到屏幕上的內容只有截頭錐體 ( View Frustum ) 部分的內容, 所以 Graph3dView 提供了 eye ,center , up ,far ,near ,fovy 和 aspect 參數來控制截頭錐體的具體範圍:oop

  • getEye() | setEye([x, y, z]) ,決定眼睛(或 Camera )所在位置,默認值爲 [0, 300, 1000]
  • getCenter() | setCenter([x, y, z]) ,決定目標中心點(或 Target )所在位置,默認值爲 [0, 0, 0]
  • getUp() | setUp([x, y, z]) ,決定攝像頭正上方向,該參數通常較少改動,默認值爲 [0, 1, 0]
  • getNear() | setNear(near) ,決定近端截面位置,默認值爲 10
  • getFar() | setFar(far) ,決定遠端截面位置,默認值爲 10000
  • getFovy() | setFovy(fovy) ,fovy 決定垂直方向的視覺張角弧度,默認值爲 Math.PI/4
  • getAspect() | setAspect(aspect) ,決定截頭錐體的寬高比,該參數默認自動根據屏幕的寬高比決定,通常不須要設置。

而後咱們再給它加上一些選中效果。Graph3dView 中被選中的圖元會顯示爲較暗的狀態,變暗係數是由圖元 style 的 brightness 和 select.brightness 屬性決定,select.brightness 屬性默認值爲 0.7,最終返回值大於 1 變亮,小於 1 變暗,等於 1 或爲空則不變化。Graph3dView#getBrightness 函數控制最終圖元亮度,所以也能夠經過重載覆蓋該函數自定義選中圖元亮度。學習

g3d.getBrightness = function (data) {
  if (data.s('isFocused')) {
     return 0.7;
   }
  return null;
};
lastFocusData = null;
g3d.getView().addEventListener('mousemove', function (e) {
  // 傳入邏輯座標點或者交互 event 事件參數,返回當前點下的圖元
   var data = g3d.getDataAt(e);
   if (data !== lastFocusData) {
     if (lastFocusData) {
        astFocusData.s('isFocused', false);
      }
      if (data) {
         data.s('isFocused', true);
      }
      astFocusData = data;
  }
});

接下來咱們要爲這些零件設置吸附:動畫

dm.getDataByTag('機櫃').setHost(dm.getDataByTag('地板'))
dm.getDataByTag('設備').setHost(dm.getDataByTag('機櫃'))
dm.getDataByTag('門').setHost(dm.getDataByTag('機櫃'))
...

吸附功能對於設計有層次關係的模型很是方便,例如設備面板吸附上設備機框,設備端口吸附上設備面板,這樣從機框 - 面板 - 端口的層次關係吸附,使得用戶拖動總體機框時全部這個層次下的圖元都會跟隨移動。對於 3D 的場景下,吸附的概念更進一步延伸,當機框在三維空間進行任意位置偏移以及任意角度旋轉時,全部吸附的相關圖元都會正確的跟隨平移,並作出相應位置對應的旋轉,以達到總體設備各個圖形部分保持物理相對位置一致。簡單來講就是當圖元吸附上宿主圖元時,宿主移動或旋轉時會帶動全部吸附者。網站

  • Node#getHost() 和 Node#setHost(node) 獲取和設置吸附的圖元對象
  • Node#getAttaches() 返回目前吸附到該圖元的全部對象,返回 ht.List 鏈表對象,無吸附對象時返回空
  • Node#isHostOn(node) 判斷該圖元是否吸附到指定圖元對象上
  • Node#isLoopedHostOn(node) 判斷該圖元是否與指定圖元相互造成環狀吸附,例如 A 吸附 B ,B 吸附 C,C 又吸附回 A,則 A,B 和 C 圖元相互環狀吸附

由於我這裏是有 6 個設備,我要把每個都給一個屬性值來記錄變化的狀態一下子用到:

dm.getDataByTag('門').a('open', false)
for (var i = 1; i < 7; i++) {
  dm.getDataByTag('設備' + i).a('open', false)
}

上一篇關於 SCADA 組態電機的隨筆裏面我們已經用到過動畫,這回讓咱們經過事件監聽在雙擊它的時候來爲其加上動畫效果:

// 監聽事件
g3d.mi(function (event) {
  if (event.kind === 'doubleClickData') {
    var tag = event.data.getTag()
    if (tag === '門') {
      if (anim) {
        anim.stop(true)
      }
      //獲取旋轉角度
      var oldAngles = event.data.getRotation(),
         angles = (open ? 2 : -2)
      //啓動動畫
      anim = ht.Default.startAnim({
        action : function(t) {
          event.data.setRotation(oldAngles + t * angles)
        },
      })
      open = !open
    }
    //檢測字符串是否以指定的前綴開始
    else if (tag.startsWith('設備')) {
      //設備動畫函數
      animation(event.data)
    }
  }
}

設備動畫的函數在這裏:

function animation(data) {
  //設置每一個設備依次的變化參數
  var v
  for (var i = 1; i < 7; i++) {
    if (tag === '設備'+ i) {
    v= i / 2
    }
  }
  if (data.anim) {     data.anim.stop(true)     data.anim = null   }   var open = data.a('open'),     p3 = data.p3(),     s3 = data.s3()   if (open) {     data.anim = ht.Default.startAnim({       action : function(t) {         data.p3(p3[0], p3[1], p3[2] + t * -s3[2] * v)       }     })     data.a('open', false)   }   else {     data.anim = ht.Default.startAnim({       action : function(t) {         data.p3(p3[0], p3[1], p3[2] + t * s3[2] * v)       }     })     data.a('open', true)   } }

這裏面有一些簡寫跟大夥兒說下。好比 mi 是增長交互事件監聽器,addInteractorListener 的縮寫,另外 event 格式有:

  • kind: 'clickData', // 事件類型
  • data: data, // 事件相關的數據元素
  • part: "part", // 事件的區域, icon 、label 等
  • event: e // html 原生事件

同時還有 3D 的一些:

  • setPosition3d(x, y, z) | setPosition3d([x, y, z]) 可簡寫爲 p3(x, y, z) | p3([x, y, z])
  • getPosition3d() 可簡寫爲 p3()
  • setSize3d(x, y, z) | setSize3d([x, y, z]) 可簡寫爲 s3(x, y, z) | s3([x, y, z])
  • getSize3d() 可簡寫爲 s3()
  • setRotation3d(x, y, z) | setRotation3d([x, y, z]) 可簡寫爲 r3(x, y, z) | r3([x, y, z])
  • getRotation3d() 可簡寫爲 r3()

建議熟記經常使用函數簡寫可提升編碼效率,可參考入門手冊中函數簡寫( http://www.hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html )。最後別忘記 g3d.addToDOM() 呦!嘿嘿~~~

總結

這個小 demo 就說到這裏吧,我會不按期的寫一些技術隨筆,既幫助本身整理知識,也可以跟你們一塊兒學習,咱們由淺至深,按部就班。但願看了個人文章能得大家帶來幫助,同時也但願你們能多多支持和鼓勵!

相關文章
相關標籤/搜索