油田系統三維布局可視化解決方案


最近和一家公司在談一個項目合作,他們公司主要是做油田相關設備的,比如油罐車、壓力車、泵車等。

我的印象中只要和石油相關的企業,就感覺和錢挨得好近,😄 。

他們老板看了我們公司的三維產品后,大為贊嘆。 驚呼,我們油田的管理最好也能上一套這樣的三維系統。

油田行業的三維可視化項目,我們之前沒有做過相關的行業,但是在三維可視化方面,我們經驗還是挺多的,比如數據中心、醫院、學校等三維可視化項目,還包括智慧園區、智慧城市、智慧小鎮的方向的等三維可視化。

下面先上幾張三維可視化的圖瞅瞅:

園區

數據中心

智慧園區

雖然我們沒有直接做過油田的三維可視化,但是有了以上三維方案的技術積累,這事做起來就不會太難。

其實客戶的需求,並不是就某個油田場景進行三維可視化的場景搭建。而是要做一個油田三維的布局工具,通過布局工具,可以自由搭建不同的油田場景。

這比直接搭建一個三維的場景要難許多。

所謂萬事開頭難,難在不開頭。 天下事有難易乎,干就完了。

在商務人員和客戶確立合同,正式立項后, 我們的設計小姐姐,開發小哥哥,都各司其職,下邊就講一下項目的大概內容。

搭建模型庫

第一步要做的就是建模,設計組使用3D建模工具 3d max或者c4d 進行油田設備模型的建模。建模后,導出后綴為obj或者gltf格式文件,這兩種格式是我們三維渲染引擎支持的最好的文件格式。

建模后的所有模型文件,最終會放到后端的模型庫,模型庫的管理目錄如下圖所示:

模型列表

加載模型

加載模型可使用引擎模型的加載函數進行模型加載,比如obj模型加載,示例代碼如下:

new mono.OBJMTLLoader().load( 'yaliche.obj', 'yaliche.mtl', '',  (node)=> {
    node.type = 'obj';
    box.addByDescendant(node);
  },
);

上面加載了一個壓力車的模型,加載模型是一個異步的過程,所以會有一個回調函數,加載完成之后,在回調函數中,把模型文件生成的三維對象加入到場景容器box之中,加入之后,場景中就會顯示我們的三維對象,如下圖所示:

壓力車

搭建編輯器框架

在和設計組、開發組一起探討之后,我們編輯器的框架和視圖初步設計出來了,大致樣子如下:

編輯器框架

視圖左上角是我們的logo,上方是工具欄。左側分為場景區和組件區,場景區是創建三維場景的列表,組件區主要是模型列表,同時還有些echarts圖表組件。

中間部分是三維場景呈現區。

對於這個頁面布局,我想不用做太多技術上的闡述,基本上會一點前端開發的人員都可以實現類似的效果。

<div class="layui-layout layui-layout-admin">
      <div class="layui-side layui-bg-black" id="leftTreeWrap">
        <div class="layui-side-scroll">
          <div class="layui-collapse" id="leftTree">
            <div class="sceneTreeWrap">
              <div class="groupTitle">
                場景
              </div>
              
            </div>
            <div class="groupTitle">
              組件
            </div>

            <div id="modelGroupWrap">
              <!-- <div class="layui-colla-item modelGroup not-select" id="groundWrap">
                <h2 class="layui-colla-title"><span>場景模型</span></h2>
                <div class="layui-colla-content" id="groundTree">
                  <div class="tree-wrap"></div>
                </div>
              </div> -->
            </div>
          </div>
        </div>
      </div>
      <div class="layui-body">
        <div class="toolbar">
          <div class="temporaryTool"></div>
        </div>
        <div class="app" tabindex="0">
          <canvas id="monoCanvas"></canvas>
        </div>
      </div>

左側邊欄包括兩個部分,一個是場景列表,一個是模型列表。場景列表是樹組件,模型列表是手風琴組件,如下圖所示:
列表

模型列表的創建過程是這樣的,首先從后端獲取所有的模型:

 getComponentTree({ params: { owner: user } }, '同步雲端組件樹失敗').then((res) => {
      if (res) {
        const treeData = res.data.data;
        treeData.forEach(({ data }) => {
          this.appendModelBtn(data, true);
        });
        // this.renderTreeDom(res.data.data);
      }
    });

通過每個模型創建模型對於的button,函數是appendModelBtn,如下:

 appendModelBtn(modelData, isNew) {
    const domWrap = this.groupDom[modelData.group];
    if (!domWrap) {
      console.log('缺少該類型對應的組', modelData.group);
      if (modelData.category === 'skyBox') {
        modelData.isNew = true;
        skyData.push({ modelData });
      }
    } else {
      domWrap.querySelector('.tree-wrap').appendChild(this.createModelBtnDom(modelData, isNew));
    }
  }

需要注意的,每個模型按鈕都需要有drag and drop的功能。在模型按鈕上需要監聽drag 或者dragstart事件,這個被封裝到一個獨立的類Dragger.js里面,在該類中專門處理了dragstart事件:

 addDragger(parent, subClass, option) {
    parent.addEventListener('dragstart', (e) => {
      let target = null;
      //  拿到冒泡的所有元素
      const path = eventPath(e);
      for (let i = 0; i < path.length; i += 1) {
        if (path[i].classList && path[i].classList.contains(subClass)) {
          target = path[i];
          break;
        }
      }
...
}

中間區域是三維呈現區域。 首先創建一個Network3D對象,Network3D對象是封裝的三維呈現頁面,其底層是由canvas組成的,並使用webgl技術進行三維渲染。下面是創建Network3D的代碼:

 const network = new mono.Network3D(box, null, 'monoCanvas');
    network.mode = 'editor';
    window.network = network; // todo
    this.network = network;
    network.bindApp(this);
    network.setRenderSelectFunction(() => false);
    make.Default.path = './static/myModellib/';

    network.setClearColor(0, 0, 0);
    network.setClearAlpha(0);

創建對象之后,讓network可以和中間區域的大小自適應:

 mono.Utils.autoAdjustNetworkBounds(
      network,
      document.querySelector('.app'),
      'clientWidth',
      'clientHeight',
    );

其中network上的box對象用於管理要加載的三維對象模型。前面說過在模型列表上增加了drag事件,模型列表上的模型,通過拖拽可以添加到network對象上去,因此在network上面也需要添加對應的事件來添加對象:

 onup: (e) => {
        if (!this.sceneTree.senceId && !window.debug) {
          layui.layer.msg('請先創建或選擇場景', {
            time: 2000,
          });
          return;
        }
        //  鼠標不在畫布內的時候不創建
        if (isPosInCanvas(network, e)) {
          network.createElement({
            e,
            configString,
            senceId: this.sceneTree.senceId,
          });
        }
      },

當模型從左側模型列表拖拽到network對象后,鼠標mouseup事件后,創建模型實例:

 network.createElement({
            e,
            configString,
            senceId: this.sceneTree.senceId,
          });

到目前為止,已經完成了整個模型列表加載,模型拖拽創建模型實例的過程。 比如最終通過拖拽的油田場景如下所示:

拖拽的油田場景

在3d場景中,需要調整三維模型的位置、旋轉角度和縮放比例,可以通過屬性面板來調整:
屬性面板

也可以通過三維編輯功能直接在三維場景中對模型進行調整標記,要使用調整編輯功能,只需要加入如下這行代碼即可:

 const editInteraction = new mono.EditInteraction(network);
    editInteraction.setScaleable(false);
    editInteraction.setRotateable(false);
    editInteraction.setTranslateable(true);
    editInteraction.setDefaultMode('');

    network.setInteractions([...network.getInteractions(), editInteraction]);

EditInteraction類 用於調整模型的位置、旋轉角度和縮放比例。 通過鍵盤可以調整EditInteraction當前要調整的是那個屬性:

case 82: // r 在有選中element時,切換操作為旋轉
          if (this.network.getIsMonoElement(this.network.currComponent)) {
            const editInteraction = this.network.getInteractions()[2];
            editInteraction.setScaleable(false);
            editInteraction.setRotateable(true);
            editInteraction.setTranslateable(false);
          }
          break;
        case 84: // t 在有選中element時,切換操作為移動
          if (this.network.getIsMonoElement(this.network.currComponent)) {
            const editInteraction = this.network.getInteractions()[2];
            editInteraction.setScaleable(false);
            editInteraction.setRotateable(false);
            editInteraction.setTranslateable(true);
          }
          break;
        case 89: // y 在有選中element時,切換操作為縮放
          if (this.network.getIsMonoElement(this.network.currComponent)) {
            const editInteraction = this.network.getInteractions()[2];
            editInteraction.setScaleable(true);
            editInteraction.setRotateable(false);
            editInteraction.setTranslateable(false);
          }
          break;

r鍵切換為旋轉角度的調整:
旋轉

t鍵切換為位置的調整:
平移

y鍵切換為縮放的調整:
縮放

拖拽創造場景之后,每個對象還可以進行實時數據的對接,對接后呈現的效果如下:
實時數據

在完成場景的創建和數據的對接之后,便可以發布場景,點擊工具欄的預覽按鈕,即可以完成場景的發布和預覽。上一張最終發布的效果圖如下:

最終效果

有興趣獲取編輯器的,請發郵件到:
terry.tan@servasoft.com
歡迎關注公眾號“ITman彪叔”。彪叔,擁有10多年開發經驗,現任公司系統架構師、技術總監、技術培訓師、職業規划師。在計算機圖形學、WebGL、前端可視化方面有深入研究。對程序員思維能力訓練和培訓、程序員職業規划有濃厚興趣。
ITman彪叔公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM