最近和一家公司在談一個項目合做,他們公司主要是作油田相關設備的,好比油罐車、壓力車、泵車等。前端
個人印象中只要和石油相關的企業,就感受和錢捱得好近,😄 。node
他們老闆看了咱們公司的三維產品後,大爲讚歎。 驚呼,咱們油田的管理最好也能上一套這樣的三維繫統。程序員
油田行業的三維可視化項目,咱們以前沒有作過相關的行業,可是在三維可視化方面,咱們經驗仍是挺多的,好比數據中心、醫院、學校等三維可視化項目,還包括智慧園區、智慧城市、智慧小鎮的方向的等三維可視化。web
下面先上幾張三維可視化的圖瞅瞅:canvas
雖然咱們沒有直接作過油田的三維可視化,可是有了以上三維方案的技術積累,這事作起來就不會太難。後端
其實客戶的需求,並非就某個油田場景進行三維可視化的場景搭建。而是要作一個油田三維的佈局工具,經過佈局工具,能夠自由搭建不一樣的油田場景。架構
這比直接搭建一個三維的場景要難許多。app
所謂萬事開頭難,難在不開頭。 天下事有難易乎,幹就完了。echarts
在商務人員和客戶確立合同,正式立項後, 咱們的設計小姐姐,開發小哥哥,都各司其職,下邊就講一下項目的大概內容。框架
第一步要作的就是建模,設計組使用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、前端可視化方面有深刻研究。對程序員思惟能力訓練和培訓、程序員職業規劃有濃厚興趣。