油田系统三维布局可视化解决方案

最近和一家公司在谈一个项目合作,他们公司主要是做油田相关设备的,比如油罐车、压力车、泵车等。

我的印象中只要和石油相关的企业,就感觉和钱挨得好近,😄 。

他们老板看了我们公司的三维产品后,大为赞叹。 惊呼,我们油田的管理最好也能上一套这样的三维系统。

油田行业的三维可视化项目,我们之前没有做过相关的行业,但是在三维可视化方面,我们经验还是挺多的,比如数据中心、医院、学校等三维可视化项目,还包括智慧园区、智慧城市、智慧小镇的方向的等三维可视化。

下面先上几张三维可视化的图瞅瞅:

园区
数据中心
智慧园区

虽然我们没有直接做过油田的三维可视化,但是有了以上三维方案的技术积累,这事做起来就不会太难。

其实客户的需求,并不是就某个油田场景进行三维可视化的场景搭建。而是要做一个油田三维的布局工具,通过布局工具,可以自由搭建不同的油田场景。

这比直接搭建一个三维的场景要难许多。

所谓万事开头难,难在不开头。 天下事有难易乎,干就完了。

在商务人员和客户确立合同,正式立项后, 我们的设计小姐姐,开发小哥哥,都各司其职,下边就讲一下项目的大概内容。

搭建模型库

第一步要做的就是建模,设计组使用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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容