美文网首页WebGL,Canvas,前端可视化
油田系统三维布局可视化解决方案

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

作者: ITman彪叔 | 来源:发表于2020-02-11 09:42 被阅读0次

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

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

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

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

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

    园区 数据中心 智慧园区

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

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

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

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

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

    搭建模型库

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

    相关文章

      网友评论

        本文标题:油田系统三维布局可视化解决方案

        本文链接:https://www.haomeiwen.com/subject/pklpactx.html