• 基于 HTML5 Canvas 的 3D 渲染引擎构建机架式服务器


    前言

    今天找到了 HT 的官网里的 Demo 网站( http://www.hightopo.com/demos/index.html ),看的我眼花缭乱,目不暇接。 而且 HT 的用户手册,将例子和文档无缝融合一体,小小 10 来兆开发包居然包含了四十五份手册,数百个活生生的 HTML5 例子,还没体验过的朋友赶紧来看一看,这回可玩嗨了!

    对于 HT 初学者,面对这一堆数百个涵括通用组件、网络拓扑图组件、3D 组件、矢量图形、各种编辑器等等五法八门的 HTML5 例子盛宴,往往无从下手。为此,老郑我打算为像我一样喜欢这方面的新手朋友多写几篇这样的博客,慢慢的给大家讲述各种各样的越来越多的有趣的小功能!

    效果图

    ( https://hightopo.com/demo/CabinetAnimat/

    代码实现

    HT 提供了基于 WebGL 的 3D 技术的图形组件 ht.graph3d.Graph3dView,WebGL 基于 OpenGL ES 2.0 图形接口,因此 WebGL 属于底层的图形 API 接口,二次开发还是有很高的门槛,HT 的 Graph3dView 组件通过对 WebGL 底层技术的封装,与 HT 其他组件一样,基于 HT 统一的 DataModel 数据模型来驱动图形显示,极大降低了 3D 图形技术开发的门槛。同时 HT 提供了强大的完全基于 HTML5 技术 3D 图形建模设计器,用户无需编码即可快速可视化搭建各种 3D 场景,可以说 HT 的 3D 开发模式完全打破了传统 3D 开发模式,绝大部分应用不再需要依赖精通 3ds Max 或 Maya 的专业 3D 设计师来建模,也不需要整合 Unity3d 等引擎做图形渲染,HT 一站式的提供了从建模到渲染,包括和 2D 组件呈现和数据融合的一站式解决方案。

    我本次讲解的就是这个 3D 的界面,所以我们首先要创建 3D 渲染引擎组件,可视化呈现数据模型的三维环境场景。

    var dm = new ht.DataModel()
    var g3d = new ht.graph3d.Graph3dView(dm)

    我们还要设置眼睛(或Camera)所在位置以及中心点(目标)的位置,格式均为 [x, y, z] 。

    g3d.setEye([-376, 270, 896])
    g3d.setCenter([-16, 118, -186])

    这里给大家说一下,可参考 3D 手册( http://www.hightopo.com/guide/guide/core/3d/ht-3d-guide.html )。如上图所示,透视投影最终显示到屏幕上的内容只有截头锥体 ( View Frustum ) 部分的内容, 因此 Graph3dView 提供了 eye ,center , up ,far ,near ,fovy 和 aspect 参数来控制截头锥体的具体范围:

    • getEye() | setEye([x, y, z]) ,决定眼睛(或 Camera )所在位置,默认值为 [0, 300, 1000]
    • getCenter() | setCenter([x, y, z]) ,决定目标中心点(或 Target )所在位置,默认值为 [0, 0, 0]
    • getUp() | setUp([x, y, z]) ,决定摄像头正上方向,该参数一般较少改动,默认值为 [0, 1, 0]
    • getNear() | setNear(near) ,决定近端截面位置,默认值为 10
    • getFar() | setFar(far) ,决定远端截面位置,默认值为 10000
    • getFovy() | setFovy(fovy) ,fovy 决定垂直方向的视觉张角弧度,默认值为 Math.PI/4
    • getAspect() | setAspect(aspect) ,决定截头锥体的宽高比,该参数默认自动根据屏幕的宽高比决定,一般不需要设置。

    然后我们再给它加上一些选中效果。Graph3dView 中被选中的图元会显示为较暗的状态,变暗系数是由图元 style 的 brightness 和 select.brightness 属性决定,select.brightness 属性默认值为 0.7,最终返回值大于 1 变亮,小于 1 变暗,等于 1 或为空则不变化。Graph3dView#getBrightness 函数控制最终图元亮度,因此也可以通过重载覆盖该函数自定义选中图元亮度。

    g3d.getBrightness = function (data) {
      if (data.s('isFocused')) {
         return 0.7;
       }
      return null;
    };
    lastFocusData = null;
    g3d.getView().addEventListener('mousemove', function (e) {
      // 传入逻辑坐标点或者交互 event 事件参数,返回当前点下的图元
       var data = g3d.getDataAt(e);
       if (data !== lastFocusData) {
         if (lastFocusData) {
            astFocusData.s('isFocused', false);
          }
          if (data) {
             data.s('isFocused', true);
          }
          astFocusData = data;
      }
    });

    接下来我们要为这些零件设置吸附:

    dm.getDataByTag('机柜').setHost(dm.getDataByTag('地板'))
    dm.getDataByTag('设备').setHost(dm.getDataByTag('机柜'))
    dm.getDataByTag('门').setHost(dm.getDataByTag('机柜'))
    ...

    吸附功能对于设计有层次关系的模型非常方便,例如设备面板吸附上设备机框,设备端口吸附上设备面板,这样从机框 - 面板 - 端口的层次关系吸附,使得用户拖动整体机框时所有这个层次下的图元都会跟随移动。对于 3D 的场景下,吸附的概念更进一步延伸,当机框在三维空间进行任意位置偏移以及任意角度旋转时,所有吸附的相关图元都会正确的跟随平移,并做出相应位置对应的旋转,以达到整体设备各个图形部分保持物理相对位置一致。简单来说就是当图元吸附上宿主图元时,宿主移动或旋转时会带动所有吸附者。

    • Node#getHost() 和 Node#setHost(node) 获取和设置吸附的图元对象
    • Node#getAttaches() 返回目前吸附到该图元的所有对象,返回 ht.List 链表对象,无吸附对象时返回空
    • Node#isHostOn(node) 判断该图元是否吸附到指定图元对象上
    • Node#isLoopedHostOn(node) 判断该图元是否与指定图元相互形成环状吸附,例如 A 吸附 B ,B 吸附 C,C 又吸附回 A,则 A,B 和 C 图元相互环状吸附

    因为我这里是有 6 个设备,我要把每一个都给一个属性值来记录变化的状态一会儿用到:

    dm.getDataByTag('门').a('open', false)
    for (var i = 1; i < 7; i++) {
      dm.getDataByTag('设备' + i).a('open', false)
    }

    上一篇关于 SCADA 组态电机的随笔里面咱们已经用到过动画,这回让我们通过事件监听在双击它的时候来为其加上动画效果:

    // 监听事件
    g3d.mi(function (event) {
      if (event.kind === 'doubleClickData') {
        var tag = event.data.getTag()
        if (tag === '门') {
          if (anim) {
            anim.stop(true)
          }
          //获取旋转角度
          var oldAngles = event.data.getRotation(),
             angles = (open ? 2 : -2)
          //启动动画
          anim = ht.Default.startAnim({
            action : function(t) {
              event.data.setRotation(oldAngles + t * angles)
            },
          })
          open = !open
        }
        //检测字符串是否以指定的前缀开始
        else if (tag.startsWith('设备')) {
          //设备动画函数
          animation(event.data)
        }
      }
    }

    设备动画的函数在这里:

    function animation(data) {
      //设置每个设备依次的变化参数
      var v
      for (var i = 1; i < 7; i++) {
        if (tag === '设备'+ i) {
        v= i / 2
        }
      }
      if (data.anim) {     data.anim.stop(true)     data.anim = null   }   var open = data.a('open'),     p3 = data.p3(),     s3 = data.s3()   if (open) {     data.anim = ht.Default.startAnim({       action : function(t) {         data.p3(p3[0], p3[1], p3[2] + t * -s3[2] * v)       }     })     data.a('open', false)   }   else {     data.anim = ht.Default.startAnim({       action : function(t) {         data.p3(p3[0], p3[1], p3[2] + t * s3[2] * v)       }     })     data.a('open', true)   } }

    这里面有一些简写跟大伙儿说下。比如 mi 是增加交互事件监听器,addInteractorListener 的缩写,另外 event 格式有:

    • kind: 'clickData', // 事件类型
    • data: data, // 事件相关的数据元素
    • part: "part", // 事件的区域, icon 、label 等
    • event: e // html 原生事件

    同时还有 3D 的一些:

    • setPosition3d(x, y, z) | setPosition3d([x, y, z]) 可简写为 p3(x, y, z) | p3([x, y, z])
    • getPosition3d() 可简写为 p3()
    • setSize3d(x, y, z) | setSize3d([x, y, z]) 可简写为 s3(x, y, z) | s3([x, y, z])
    • getSize3d() 可简写为 s3()
    • setRotation3d(x, y, z) | setRotation3d([x, y, z]) 可简写为 r3(x, y, z) | r3([x, y, z])
    • getRotation3d() 可简写为 r3()

    建议熟记常用函数简写可提高编码效率,可参考入门手册中函数简写( http://www.hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html )。最后别忘记 g3d.addToDOM() 呦!嘿嘿~~~

    总结

    这个小 demo 就说到这里吧,我会不定期的写一些技术随笔,既帮助自己整理知识,也能够跟大家一起学习,我们由浅至深,循序渐进。希望看了我的文章能得你们带来帮助,同时也希望大家能多多支持和鼓励!

  • 相关阅读:
    加分二叉树
    飞扬的小鸟
    洛谷P2066 机器分配
    解方程
    洛谷P1781 宇宙总统
    洛谷P1311 选择客栈
    洛谷P1081 开车旅行70分
    CSS清除浮动
    常见的内联元素与块状元素
    标签的权值问题(优先级)
  • 原文地址:https://www.cnblogs.com/htdaydayup/p/9778453.html
Copyright © 2020-2023  润新知