前言
今天找到了 HT 的官網里的 Demo 網站( http://www.hightopo.com/demos/index.html ),看的我眼花繚亂,目不暇接。 而且 HT 的用戶手冊,將例子和文檔無縫融合一體,小小 10 來兆開發包居然包含了四十五份手冊,數百個活生生的 HTML5 例子,還沒體驗過的朋友趕緊來看一看,這回可玩嗨了!
對於 HT 初學者,面對這一堆數百個涵括通用組件、網絡拓撲圖組件、3D 組件、矢量圖形、各種編輯器等等五法八門的 HTML5 例子盛宴,往往無從下手。為此,老鄭我打算為像我一樣喜歡這方面的新手朋友多寫幾篇這樣的博客,慢慢的給大家講述各種各樣的越來越多的有趣的小功能!
效果圖

( 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 渲染引擎組件,可視化呈現數據模型的三維環境場景。
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 參數來控制截頭錐體的具體范圍:
- 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 就說到這里吧,我會不定期的寫一些技術隨筆,既幫助自己整理知識,也能夠跟大家一起學習,我們由淺至深,循序漸進。希望看了我的文章能得你們帶來幫助,同時也希望大家能多多支持和鼓勵!
