最近和一家公司在談一個項(xiàng)目合作,他們公司主要是做油田相關(guān)設(shè)備的渐裸,比如油罐車、壓力車装悲、泵車等昏鹃。
我的印象中只要和石油相關(guān)的企業(yè),就感覺和錢挨得好近诀诊,?? 洞渤。
他們老板看了我們公司的三維產(chǎn)品后,大為贊嘆属瓣。 驚呼载迄,我們油田的管理最好也能上一套這樣的三維系統(tǒng)。
油田行業(yè)的三維可視化項(xiàng)目抡蛙,我們之前沒有做過相關(guān)的行業(yè)护昧,但是在三維可視化方面,我們經(jīng)驗(yàn)還是挺多的溜畅,比如數(shù)據(jù)中心捏卓、醫(yī)院、學(xué)校等三維可視化項(xiàng)目慈格,還包括智慧園區(qū)怠晴、智慧城市、智慧小鎮(zhèn)的方向的等三維可視化浴捆。
下面先上幾張三維可視化的圖瞅瞅:
雖然我們沒有直接做過油田的三維可視化蒜田,但是有了以上三維方案的技術(shù)積累,這事做起來就不會太難选泻。
其實(shí)客戶的需求冲粤,并不是就某個油田場景進(jìn)行三維可視化的場景搭建美莫。而是要做一個油田三維的布局工具,通過布局工具梯捕,可以自由搭建不同的油田場景厢呵。
這比直接搭建一個三維的場景要難許多。
所謂萬事開頭難傀顾,難在不開頭襟铭。 天下事有難易乎,干就完了短曾。
在商務(wù)人員和客戶確立合同寒砖,正式立項(xiàng)后, 我們的設(shè)計小姐姐嫉拐,開發(fā)小哥哥哩都,都各司其職,下邊就講一下項(xiàng)目的大概內(nèi)容。
搭建模型庫
第一步要做的就是建模婉徘,設(shè)計組使用3D建模工具 3d max或者c4d 進(jìn)行油田設(shè)備模型的建模漠嵌。建模后,導(dǎo)出后綴為obj或者gltf格式文件判哥,這兩種格式是我們?nèi)S渲染引擎支持的最好的文件格式献雅。
建模后的所有模型文件碉考,最終會放到后端的模型庫塌计,模型庫的管理目錄如下圖所示:
加載模型
加載模型可使用引擎模型的加載函數(shù)進(jìn)行模型加載,比如obj模型加載侯谁,示例代碼如下:
new mono.OBJMTLLoader().load( 'yaliche.obj', 'yaliche.mtl', '', (node)=> {
node.type = 'obj';
box.addByDescendant(node);
},
);
上面加載了一個壓力車的模型锌仅,加載模型是一個異步的過程,所以會有一個回調(diào)函數(shù)墙贱,加載完成之后热芹,在回調(diào)函數(shù)中,把模型文件生成的三維對象加入到場景容器box之中惨撇,加入之后伊脓,場景中就會顯示我們的三維對象,如下圖所示:
搭建編輯器框架
在和設(shè)計組极颓、開發(fā)組一起探討之后喜庞,我們編輯器的框架和視圖初步設(shè)計出來了偎谁,大致樣子如下:
視圖左上角是我們的logo,上方是工具欄纯蛾。左側(cè)分為場景區(qū)和組件區(qū),場景區(qū)是創(chuàng)建三維場景的列表纵隔,組件區(qū)主要是模型列表翻诉,同時還有些echarts圖表組件炮姨。
中間部分是三維場景呈現(xiàn)區(qū)。
對于這個頁面布局碰煌,我想不用做太多技術(shù)上的闡述舒岸,基本上會一點(diǎn)前端開發(fā)的人員都可以實(shí)現(xiàn)類似的效果。
<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>
左側(cè)邊欄包括兩個部分芦圾,一個是場景列表吁津,一個是模型列表。場景列表是樹組件堕扶,模型列表是手風(fēng)琴組件碍脏,如下圖所示:
模型列表的創(chuàng)建過程是這樣的,首先從后端獲取所有的模型:
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);
}
});
通過每個模型創(chuàng)建模型對于的button稍算,函數(shù)是appendModelBtn典尾,如下:
appendModelBtn(modelData, isNew) {
const domWrap = this.groupDom[modelData.group];
if (!domWrap) {
console.log('缺少該類型對應(yīng)的組', 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的功能糊探。在模型按鈕上需要監(jiān)聽drag 或者dragstart事件钾埂,這個被封裝到一個獨(dú)立的類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;
}
}
...
}
中間區(qū)域是三維呈現(xiàn)區(qū)域科平。 首先創(chuàng)建一個Network3D對象褥紫,Network3D對象是封裝的三維呈現(xiàn)頁面,其底層是由canvas組成的瞪慧,并使用webgl技術(shù)進(jìn)行三維渲染髓考。下面是創(chuàng)建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);
創(chuàng)建對象之后,讓network可以和中間區(qū)域的大小自適應(yīng):
mono.Utils.autoAdjustNetworkBounds(
network,
document.querySelector('.app'),
'clientWidth',
'clientHeight',
);
其中network上的box對象用于管理要加載的三維對象模型弃酌。前面說過在模型列表上增加了drag事件氨菇,模型列表上的模型,通過拖拽可以添加到network對象上去妓湘,因此在network上面也需要添加對應(yīng)的事件來添加對象:
onup: (e) => {
if (!this.sceneTree.senceId && !window.debug) {
layui.layer.msg('請先創(chuàng)建或選擇場景', {
time: 2000,
});
return;
}
// 鼠標(biāo)不在畫布內(nèi)的時候不創(chuàng)建
if (isPosInCanvas(network, e)) {
network.createElement({
e,
configString,
senceId: this.sceneTree.senceId,
});
}
},
當(dāng)模型從左側(cè)模型列表拖拽到network對象后查蓉,鼠標(biāo)mouseup事件后,創(chuàng)建模型實(shí)例:
network.createElement({
e,
configString,
senceId: this.sceneTree.senceId,
});
到目前為止榜贴,已經(jīng)完成了整個模型列表加載豌研,模型拖拽創(chuàng)建模型實(shí)例的過程。 比如最終通過拖拽的油田場景如下所示:
在3d場景中唬党,需要調(diào)整三維模型的位置鹃共、旋轉(zhuǎn)角度和縮放比例,可以通過屬性面板來調(diào)整:
也可以通過三維編輯功能直接在三維場景中對模型進(jìn)行調(diào)整標(biāo)記初嘹,要使用調(diào)整編輯功能及汉,只需要加入如下這行代碼即可:
const editInteraction = new mono.EditInteraction(network);
editInteraction.setScaleable(false);
editInteraction.setRotateable(false);
editInteraction.setTranslateable(true);
editInteraction.setDefaultMode('');
network.setInteractions([...network.getInteractions(), editInteraction]);
EditInteraction類 用于調(diào)整模型的位置、旋轉(zhuǎn)角度和縮放比例屯烦。 通過鍵盤可以調(diào)整EditInteraction當(dāng)前要調(diào)整的是那個屬性:
case 82: // r 在有選中element時坷随,切換操作為旋轉(zhuǎn)
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鍵切換為旋轉(zhuǎn)角度的調(diào)整:
t鍵切換為位置的調(diào)整:
y鍵切換為縮放的調(diào)整:
拖拽創(chuàng)造場景之后温眉,每個對象還可以進(jìn)行實(shí)時數(shù)據(jù)的對接缸匪,對接后呈現(xiàn)的效果如下:
在完成場景的創(chuàng)建和數(shù)據(jù)的對接之后,便可以發(fā)布場景类溢,點(diǎn)擊工具欄的預(yù)覽按鈕凌蔬,即可以完成場景的發(fā)布和預(yù)覽。上一張最終發(fā)布的效果圖如下:
有興趣獲取編輯器的闯冷,請發(fā)郵件到:
terry.tan@servasoft.com