前言
三維場景時常需要一個導航標識,用來確定場景所處的方位。
一般有兩種表現形式:指南針、小方盒(方位魔方)。
參考一下百度百科中的 maya 界面,可以看到右上角有一個標識方位的小盒子,說的就是它:
Hightopo 的 HT for Web 產品可以很方便地構造輕量化的 3D 可視化場景,在 web 端 我們可以利用 HT 2D 引擎 和 3D 渲染引擎 來實現這個功能,搭建一個簡易的類 maya 操作界面。
預覽地址: https://www.hightopo.com/demo/compass-and-directionbox/
界面簡介及效果預覽
在這個界面里面我們用到了一個二維場景和兩個三維場景,具體效果如下:
功能實現
先來描述一下頁面布局:
指南針 通過在 ht.graph.GraphView 中給一個圖元設置一個事先繪制好的圖標來實現,只需把它放在圖紙的左上角(即下圖中的位置 1)即可。
方位魔方 通過在一個小場景 (ht.graph3d.Graph3dView)中放置一個魔方 obj 模型來實現,然后把這個小場景放置在圖紙的右上角(即下圖中的位置 2) 即可。
主三維場景(ht.graph3d.Graph3dView)作為背景放置在整個二維頁面的下方(即下圖中的位置 3)。
代碼示例:
1 const g3d = new ht.graph3d.Graph3dView(); 2 g3d.setOriginAxisVisible(true); 3 g3d.setGridVisible(true); 4 g3d.addToDOM(); 5 const g2d = new ht.graph.GraphView(); 6 g2d.deserialize('displays/test.json', json => { 7 g2d.addToDOM(g3d.getView()); 8 });
位置關系:
指南針同步
先約定一下方位,我們將 Z 軸的負半軸的方向作為北方,Z 軸正半軸作為南方,X 軸的正半軸作為東方,X 軸的負半軸作為西方。
由於 指南針 的目的是用於指示鳥瞰圖中的方位,所以與 Y 軸並沒有什么關系,我們可以將整個計算過程放在二維空間中進行。
代碼示例:
1 const eye = this.g3d.getEye(); 2 const center = this.g3d.getCenter(); 3 const v = new ht.Math.Vector2(eye[0], eye[2]); 4 const v2 = new ht.Math.Vector2(center[0], center[2]); 5 const angle = v.sub(v2).angle() - Math.PI / 2; 6 compass.setRotation(-angle); 7 compass.a('angle', angle); 8 compass.a('angle2', angle);
在這段代碼中,我們用 eye (相機) 和 center (觀測點)來構建兩個二維向量 (ht.Math.Vector2),舍棄掉 Y 軸上的分量。
利用向量減法,求得由 center 指向 eye 的向量並存入變量 v 中,利用 angle() 方法可以獲取到當前向量與 x 正半軸 (即正東方向)的夾角(弧度制),為什么要減去 Math.PI / 2 呢,因為我們計算求得的是與 x 軸的夾角,而指南針的正方向(北方)是對應着 z 軸的負半軸。
求得了旋轉角度后,通過 setRotation() 方法我們可以設置 指南針 圖元的旋轉角度,為什么要取一個負值(- angle)?因為當視線逆時針轉動的時候,坐標軸 和 指南針 相對於人眼是沿反方向運動的,也就是順時針旋轉。
利用 HT 2D 引擎提供的 數據綁定 的功能,輪盤圖標 和 角度圖標 的旋轉角度可以通過給 compass 這個節點設置屬性值來實時動態改變。
每一次視線發生改變都需要進行如上的計算和設置,我們可以通過給三維場景組件增加一個屬性監聽器來實現:
1 graph3dView.addPropertyChangeListener(e=>{ 2 if(e.property === 'eye' || e.property === 'center'){ 3 changeCompass(); 4 //... 5 } 6 });
圖例參考:
方位魔方同步
先約定一下方位,X 正半軸為右,負半軸為左; Y 正半軸為頂,負半軸為底;Z 正半軸為前,負半軸為后。
方位魔方不同於指南針,它用於呈現三維空間中的視線方位。
與此同時,它也是一個可以交互的方位操縱桿,可以方便快捷的將當前視角變為頂視圖、側視圖等。
視線改變觸發魔方變換
代碼示例:
1 graph3dView.addPropertyChangeListener(e => { 2 if (e.property === 'eye') { 3 const newValue = e.newValue; 4 const vEye = new ht.Math.Vector3(newValue[0], newValue[1], newValue[2]).normalize(); 5 graph3dView2.setEye([300 * vEye.x, 300 * vEye.y, 300 * vEye.z]); 6 } 7 });
在上述代碼中我們通過監聽主三維場景(graph3dView) 中 eye 屬性的變化來動態改變小場景(graph3dView2) 中的 eye 的位置, 來達到聯動的效果。
其中,e.newValue 會獲取到場景視點改變后的值,我們用這個值構建一個三維向量(ht.Math.Vector3)並調用 normalize() 方法進行歸一化,這樣可以使得任何角度、位置求得的距離都保持一致。
將求得的分量乘以 300 的原因在於這個距離觀測小方塊不大不小剛合適,當然也可以根據需要改成別的值。
效果示例:
點擊魔方改變場景視角
要想實現點擊魔方來改變主場景中的視線,需要一個非常關鍵的信息,那就是鼠標究竟點擊了小魔方的哪一個面。
在這里我們需要用到一個求交點的方法: graph3dView.intersectObject(event, data),該方法會返回一個對象,該對象用於描述點擊的位置信息, 其中 world 屬性用來表示點擊位置的世界坐標。
代碼示例:
1 graph3dView2.addInteractorListener(event => { 2 if (event.kind === 'clickData') { 3 const obj = graph3dView2.intersectObject(event.event, event.data); 4 if(obj) { 5 const world = obj.world; 6 //... 7 } 8 } 9 });
拿到了這個描述點擊位置的 world 屬性我們就可以比較輕松地算出點擊了哪個面,因為我們的小方塊是放置在原點處,並且它是規則的六面體,這兩個關鍵信息決定了無論點擊它的哪一個面,所點擊的那個面它所對應的軸的分量的值一定會大於它在另外兩個軸的分量,因此我們可以簡單的判斷三分量中哪個值較大就能確定視線更靠近哪個軸,然后通過判斷分量的正負號來判斷是在正半軸還是負半軸。
判斷了出了點擊的哪個面之后,只需要在兩個三維場景中分別設置各自視點(eye) 的位置即可。
代碼示例:
1 const world = obj.world; 2 const x = world.x; 3 const y = world.y; 4 const z = world.z; 5 if (Math.abs(x) - Math.abs(y) > 0 && Math.abs(x) - Math.abs(z) > 0) { 6 if (x > 0) { 7 graph3dView2.setEye([300, 0, 0]); 8 graph3dView.setEye([this._distance, 0, 0]); 9 graph3dView2.setCenter([0, 0, 0]); 10 this._g3d.setCenter([0, 0, 0]); 11 } else { 12 graph3dView2.setEye([-300, 0, 0]); 13 graph3dView.setEye([-this._distance, 0, 0]); 14 graph3dView2.setCenter([0, 0, 0]); 15 graph3dView.setCenter([0, 0, 0]); 16 } 17 } else if (Math.abs(y) - Math.abs(x) > 0 && Math.abs(y) - Math.abs(z) > 0) { 18 //... 19 }
其中,this._distance 是用來描述主場景中視線與原點的距離,可根據需要來調整,300 與之前的描述一致,是小場景中一個比較合適的視角位置,也可以根據需要調整。
最后我們還需要處理一下小方塊點擊變色的問題(這也不見得是個問題,視需求而定),可以在點擊事件監聽器的最后做如下設置:
1 const sm = graph3dView2.dm().getSelectionModel(); 2 sm.setSelection(null);
點擊魔方各個面效果演示:
總結
直觀的方位指示在室內定位、GIS、車站、機場等諸多場景中有着廣泛的應用,利用 HT 提供的二三維引擎可以輕松地實現。
web 3D 有無限的想象空間,有着非常豐富的數據呈現方式,更有着諸多讓人眼前一亮的可視化效果,等着我們去將這些數據呈現方式在各個行業中落地,HT 在這方面做了大量的探索和嘗試,例如這個好玩兒的太陽系監控系統:https://www.hightopo.com/demo/solar-system/
2019 我們也更新了數百個工業互聯網 2D/3D 可視化案例集,在這里你能發現許多新奇的實例,也能發掘出不一樣的工業互聯網:《分享數百個 HT 工業互聯網 2D 3D 可視化應用案例之 2019 篇》,更多行業應用實例可以參考官網案例鏈接:
https://www.hightopo.com/demos/index.html