1. 程式人生 > 實用技巧 >分享 HT 實用技巧:實現指南針和 3D 魔方導航

分享 HT 實用技巧:實現指南針和 3D 魔方導航

前言

  三維場景時常需要一個導航標識,用來確定場景所處的方位。

  一般有兩種表現形式:指南針、小方盒(方位魔方)。

  參考一下百度百科中的maya介面,可以看到右上角有一個標識方位的小盒子,說的就是它:

  HightopoHT 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工業網際網路2D3D視覺化應用案例之2019篇》,更多行業應用例項可以參考官網案例連結:

https://www.hightopo.com/demos/index.html