前言
今天为大家带来一个很酷的作品,依然运用了强大的 的 3D 图形组件,动作流畅性能好,大家可以先来欣赏一下效果!
代码实现
做完场景后,首先我们要对它进行一些基本的设置,如:
// 设置 camera 的位置gv.setEye([457, 9047, 434])// 设置中心点位置gv.setCenter([-4, -1, 0])// 设置远端距离gv.setFar(500000)复制代码
设置后可以让场景在反序列化后能够显示出我们想要的展示角度,设置远端位置能够避免造成场景显示不完全等问题。
进入动画
为了使其看起来有一个进入的过程,我们给场景增加一个入场的动画来增色:
ht.Default.startAnim({ duration: 3000, // 动画周期毫秒数,默认采用`ht.Default.animDuration` action: function (v) { // action 函数必须提供,实现动画过程中的属性变化。 gv.setEye([gv.getEye()[0] + (1117 - gv.getEye()[0]) * (v / 5), gv.getEye()[1] + (450 - gv.getEye()[1]) * (v / 5), gv.getEye()[2] + (1139 - gv.getEye()[2]) * (v / 5)]) }, finishFunc: function () { // 动画结束后调用的函数。 gv.scene = { eye: ht.Default.clone(gv.getEye()), center: ht.Default.clone(gv.getCenter()), far: ht.Default.clone(gv.getFar()), near: ht.Default.clone(gv.getNear()) } }})复制代码
这个动画我们的思路是通过改变 camera 的位置来的实现,使用动画函数我们可以在指定的时间周期内完成动画,可理解为将某些属性由起始值逐渐变到目标值的过程。通过在 action 函数中我们对 carmera 进行细致地调整,就可以实现完美的入场效果了。finishFunc 函数中我们做了一个复制的操作,目的是要记住这个位置,以便于我们后面的功能实现,这个稍后会提到。
视角控制
对了,我们还要对整个场景的视角及范围做限制:
var mapInteractor = new ht.graph3d.MapInteractor(gv)gv.setInteractors([ mapInteractor])gv.mp(function (e) { if (e.property === "eye") { if (gv.getEye()[0] > 3500) { gv.getEye()[0] = 3500 } if (gv.getEye()[0] < -3500) { gv.getEye()[0] = -3500 } if (gv.getEye()[1] > 9000) { gv.getEye()[1] = 9000 } if (gv.getEye()[2] > 3500) { gv.getEye()[2] = 3500 } if (gv.getEye()[2] < -3500) { gv.getEye()[2] = -3500 } }})复制代码
这样可以限制翻转到场景底面,然后再对 eye 做限制防止在拉远的时候超出天空球包裹的范围。
接下来我们要加一个场景视角复位的功能:
gv.mi(function (e) { if (e.kind === 'doubleClickBackground') { gv.moveCamera(gv.scene.eye, gv.scene.center, true) } ...})复制代码
事件监听一下,在双击的时候通过 moveCamera() 来移动中心点的位置,坐标就是我们在入场动画的操作中记录的位置。
为了加强性能及便利性,我们在点击事件中再添加一个控制面板开关的的逻辑,这样可以简约化显示:
if (e.kind === 'clickData') { if (e.data.getTag() === '按钮') { var status = dm.getDataByTag('面板1').s('3d.visible') for (var i = 1; i <= 10; i++) { dm.getDataByTag('面板' + i).s('3d.visible', !status) } }}复制代码
通过对 2D 面板的属性改变来实现如下效果:
动画实现
然后我们要把管道的流动、履带的运行、回转窑的运动以及磨轮转动等动画先实现出来:
function flow(name1, name2) { for (var i = 1; i <= 6; i++) { // uv 偏移 if (name2) { dm.getDataByTag(name2 + i).s('shape3d.uv.offset', [dm.getDataByTag(name2 + i).s('shape3d.uv.offset')[0] + 0.005, dm.getDataByTag(name2 + i).s('shape3d.uv.offset')[1]]) } // 设默认值 else { dm.getDataByTag(name1 + i).s('shape3d.uv.offset', [0,0]) } }}// 储料罐function tank(name, num, v) { for (var i = 1; i <= 8; i++) { dm.getDataByTag(name + i).setScaleTall(dm.getDataByTag(name + i).getScaleTall() + (num[i - 1] - dm.getDataByTag(name + i).getScaleTall()) * v) }}// 滚轮function roller(name) { for (var i = 1; i <= 12; i++) { if (i % 2 === 0) { value = -0.1 } else { value = 0.1 } if (i <= 8) { dm.getDataByTag(name + i).r3(dm.getDataByTag(name + i).r3()[0], dm.getDataByTag(name + i).r3()[1] + value, dm.getDataByTag(name + i).r3()[2]) } else { dm.getDataByTag(name + i).r3(dm.getDataByTag(name + i).r3()[0] + value, dm.getDataByTag(name + i).r3()[1], dm.getDataByTag(name + i).r3()[2]) } }}anim()function anim() { var num = [] for (var i = 1; i <= 8; i++) { num.push(Math.random() * 6) } ht.Default.startAnim({ duration: 1000, action: function (v, t) { dm.getDataByTag('流动2').r3(dm.getDataByTag('流动2').r3()[0] - 0.1, dm.getDataByTag('流动2').r3()[1], dm.getDataByTag('流动2').r3()[2]) flow('流动', '流动') tank('储料罐', num, v) roller('滚轮') }, finishFunc: function () { anim() } })}复制代码
我把他们统一放在一个动画函数中循环播放,都是一些比较简单的动画,通过使高度、角度等属性的变化来实现相应的动画效果,如代码所示不一一细述。这里我稍微说一下关于这个管道和履带流动的实现思路,我是利用了调整 UV 贴图来完成的。
什么是 UV ?通俗的讲,UV 就是把三维立体模型的外表面剥离下来,展开铺平成二维平面状态,以便进行贴图绘制,就如同香烟盒上的包装图案其实是在纸盒片状态下印刷完成的一样。
var truck1 = dm.getDataByTag('卡车1')var truck2 = dm.getDataByTag('卡车2')var truck3 = dm.getDataByTag('卡车3')var cargo1 = dm.getDataByTag('货斗1')var cargo2 = dm.getDataByTag('货斗2')var coal = dm.getDataByTag('货1')var limestone = dm.getDataByTag('货2')var panel1 = dm.getDataByTag('面板8')var panel2 = dm.getDataByTag('面板9')anim1()// 出发function anim1() { ht.Default.startAnim({ duration: 4000, easing: function (t) { return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 - (--t) * (t - 2)) }, action: function (v, t) { truck1.p3(truck1.p3()[0], truck1.p3()[1], truck1.p3()[2] + (700 - truck1.p3()[2]) * (v / 10)) truck2.p3(truck2.p3()[0], truck2.p3()[1], truck2.p3()[2] + (700 - truck2.p3()[2]) * (v / 5)) truck3.p3(truck3.p3()[0] + (300 - truck3.p3()[0]) * (v / 20), truck3.p3()[1], truck3.p3()[2]) }, finishFunc: function () { anim2() } })}// 掉头function anim2() { ht.Default.startAnim({ duration: 1000, action: function (v, t) { truck1.r3(truck1.r3()[0], truck1.r3()[1] + (180 * Math.PI / 180 - truck1.r3()[1]) * v, truck1.r3()[2]) truck2.r3(truck2.r3()[0], truck2.r3()[1] + (180 * Math.PI / 180 - truck2.r3()[1]) * v, truck2.r3()[2]) truck3.r3(truck3.r3()[0], truck3.r3()[1] + (-90 * Math.PI / 180 - truck3.r3()[1]) * v, truck3.r3()[2]) }, finishFunc: function () { anim3() } })}// 卸货function anim3() { ht.Default.startAnim({ duration: 2000, action: function (v, t) { cargo1.r3(cargo1.r3()[0] + (70 * Math.PI / 180 - cargo1.r3()[0]) * v, cargo1.r3()[1], cargo1.r3()[2]) cargo2.r3(cargo2.r3()[0] + (70 * Math.PI / 180 - cargo2.r3()[0]) * v, cargo2.r3()[1], cargo2.r3()[2]) panel1.a('进度值', panel1.a('进度值') + (0 - panel1.a('进度值')) * v) panel2.a('进度值', panel2.a('进度值') + (0 - panel2.a('进度值')) * v) panel1.a('重量', panel1.a('进度值').toFixed(1)) panel2.a('重量', panel2.a('进度值').toFixed(1)) }, finishFunc: function () { coal.s('3d.visible', false) limestone.s('3d.visible', false) anim4() } })}// 卸货function anim4() { ht.Default.startAnim({ duration: 2000, action: function (v, t) { cargo1.r3(cargo1.r3()[0] + (0 * Math.PI / 180 - cargo1.r3()[0]) * v, cargo1.r3()[1], cargo1.r3()[2]) cargo2.r3(cargo2.r3()[0] + (0 * Math.PI / 180 - cargo2.r3()[0]) * v, cargo2.r3()[1], cargo2.r3()[2]) }, finishFunc: function () { anim5() } })}// 返回function anim5() { ht.Default.startAnim({ duration: 4000, easing: function (t) { return (t *= 2) < 1 ? 0.5 * t * t : 0.5 * (1 - (--t) * (t - 2)) }, action: function (v, t) { truck1.p3(truck1.p3()[0], truck1.p3()[1], truck1.p3()[2] + (1180 - truck1.p3()[2]) * (v / 10)) truck2.p3(truck2.p3()[0], truck2.p3()[1], truck2.p3()[2] + (1180 - truck2.p3()[2]) * (v / 5)) truck3.p3(truck3.p3()[0] + (1180 - truck3.p3()[0]) * (v / 20), truck3.p3()[1], truck3.p3()[2]) }, finishFunc: function () { anim6() } })}// 掉头function anim6() { ht.Default.startAnim({ duration: 1000, action: function (v, t) { truck1.r3(truck1.r3()[0], truck1.r3()[1] + (0 * Math.PI / 180 - truck1.r3()[1]) * v, truck1.r3()[2]) truck2.r3(truck2.r3()[0], truck2.r3()[1] + (0 * Math.PI / 180 - truck2.r3()[1]) * v, truck2.r3()[2]) truck3.r3(truck3.r3()[0], truck3.r3()[1] + (90 * Math.PI / 180 - truck3.r3()[1]) * v, truck3.r3()[2]) }, finishFunc: function () { panel1.a('进度值', 1) panel2.a('进度值', 1) panel1.a('重量', 10) panel2.a('重量', 10) coal.s('3d.visible', true) limestone.s('3d.visible', true) anim1() } })}复制代码
这个是我实现卡车的整个运作流程的完整代码,分别由几段动画协调组合而成,只要搞清楚顺序以及每一个动作实现的逻辑并不难办到,无非就是方向、角度和距离的一些计算,还有面板进度条同步的设置。
总结
在互联网+ 概念飞速发展的今天,有太多的领域在等待着我们去挖掘,HT for Web 非常适用于各种的智慧建筑,监控系统以及电力、燃气等工业自动化 ( HMI / SCADA ) 领域。希望看了我的这篇文章,大家能有所启发,挑战更多的不可能!