分享 HT 实用技巧:实现指南针和 3D 魔方导航
前言
三維場景時常需要一個導航標識,用來確定場景所處的方位。
一般有兩種表現(xiàn)形式:指南針、小方盒(方位魔方)。
參考一下百度百科中的 maya 界面,可以看到右上角有一個標識方位的小盒子,說的就是它:
Hightopo 的 HT for Web 產(chǎn)品可以很方便地構造輕量化的 3D 可視化場景,在 web 端我們可以利用 HT 2D 引擎和3D渲染引擎來實現(xiàn)這個功能,搭建一個簡易的類 maya 操作界面。
預覽地址:https://www.hightopo.com/demo/compass-and-directionbox/
界面簡介及效果預覽
在這個界面里面我們用到了一個二維場景和兩個三維場景,具體效果如下:
功能實現(xiàn)
先來描述一下頁面布局:
指南針通過在 ht.graph.GraphView 中給一個圖元設置一個事先繪制好的圖標來實現(xiàn),只需把它放在圖紙的左上角(即下圖中的位置 1)即可。
方位魔方 通過在一個小場景 (ht.graph3d.Graph3dView)中放置一個魔方 obj 模型來實現(xiàn),然后把這個小場景放置在圖紙的右上角(即下圖中的位置 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引擎提供的數(shù)據(jù)綁定的功能,輪盤圖標和 角度圖標的旋轉角度可以通過給 compass 這個節(jié)點設置屬性值來實時動態(tài)改變。
每一次視線發(fā)生改變都需要進行如上的計算和設置,我們可以通過給三維場景組件增加一個屬性監(jiān)聽器來實現(xiàn):
1 graph3dView.addPropertyChangeListener(e=>{
2 if(e.property === 'eye' || e.property === 'center'){
3 changeCompass();
4 //...
5 }
6 });
圖例參考:
方位魔方同步
先約定一下方位,X 正半軸為右,負半軸為左; Y 正半軸為頂,負半軸為底;Z 正半軸為前,負半軸為后。
方位魔方不同于指南針,它用于呈現(xiàn)三維空間中的視線方位。
與此同時,它也是一個可以交互的方位操縱桿,可以方便快捷的將當前視角變?yōu)轫斠晥D、側視圖等。
視線改變觸發(fā)魔方變換
代碼示例:
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 });
在上述代碼中我們通過監(jiān)聽主三維場景(graph3dView) 中 eye 屬性的變化來動態(tài)改變小場景(graph3dView2) 中的 eye 的位置, 來達到聯(lián)動的效果。
其中,e.newValue 會獲取到場景視點改變后的值,我們用這個值構建一個三維向量(ht.Math.Vector3)并調用 normalize() 方法進行歸一化,這樣可以使得任何角度、位置求得的距離都保持一致。
將求得的分量乘以 300 的原因在于這個距離觀測小方塊不大不小剛合適,當然也可以根據(jù)需要改成別的值。
效果示例:
點擊魔方改變場景視角
要想實現(xiàn)點擊魔方來改變主場景中的視線,需要一個非常關鍵的信息,那就是鼠標究竟點擊了小魔方的哪一個面。
在這里我們需要用到一個求交點的方法: 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 屬性我們就可以比較輕松地算出點擊了哪個面,因為我們的小方塊是放置在原點處,并且它是規(guī)則的六面體,這兩個關鍵信息決定了無論點擊它的哪一個面,所點擊的那個面它所對應的軸的分量的值一定會大于它在另外兩個軸的分量,因此我們可以簡單的判斷三分量中哪個值較大就能確定視線更靠近哪個軸,然后通過判斷分量的正負號來判斷是在正半軸還是負半軸。
判斷了出了點擊的哪個面之后,只需要在兩個三維場景中分別設置各自視點(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 是用來描述主場景中視線與原點的距離,可根據(jù)需要來調整,300 與之前的描述一致,是小場景中一個比較合適的視角位置,也可以根據(jù)需要調整。
最后我們還需要處理一下小方塊點擊變色的問題(這也不見得是個問題,視需求而定),可以在點擊事件監(jiān)聽器的最后做如下設置:
1 const sm = graph3dView2.dm().getSelectionModel(); 2 sm.setSelection(null);
點擊魔方各個面效果演示:
總結
直觀的方位指示在室內定位、GIS、車站、機場等諸多場景中有著廣泛的應用,利用 HT 提供的二三維引擎可以輕松地實現(xiàn)。
web 3D 有無限的想象空間,有著非常豐富的數(shù)據(jù)呈現(xiàn)方式,更有著諸多讓人眼前一亮的可視化效果,等著我們去將這些數(shù)據(jù)呈現(xiàn)方式在各個行業(yè)中落地,HT 在這方面做了大量的探索和嘗試,例如這個好玩兒的太陽系監(jiān)控系統(tǒng):https://www.hightopo.com/demo/solar-system/
2019我們也更新了數(shù)百個工業(yè)互聯(lián)網(wǎng)2D/3D可視化案例集,在這里你能發(fā)現(xiàn)許多新奇的實例,也能發(fā)掘出不一樣的工業(yè)互聯(lián)網(wǎng):《分享數(shù)百個HT工業(yè)互聯(lián)網(wǎng)2D3D可視化應用案例之2019篇》,更多行業(yè)應用實例可以參考官網(wǎng)案例鏈接:
https://www.hightopo.com/demos/index.html
總結
以上是生活随笔為你收集整理的分享 HT 实用技巧:实现指南针和 3D 魔方导航的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Invoia 推出狗狗智能项圈:可跟踪心
- 下一篇: 用于芯片生产的氖气开始降价