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