HTML5 WebGL 实现民航客机飞行监控系统


Posted in HTML / CSS onJuly 25, 2019

前言

前些日子出差,在飞机上看到头顶的监控面板,除了播放电视剧和广告之外,还会时不时的切换到一个飞机航行的监控系统,不过整个监控系统让人感到有一点点的简陋,所以我就突发奇想制作了一个采用 HT for Web 的升级版监控系统,demo 的效果还行,发出来大家相互学习下。

HTML5 WebGL 实现民航客机飞行监控系统
 

demo

实现过程

云中穿行效果

为了达到飞机云中穿行的效果,最开始我遇到的问题是飞机飞行的层次感,也就通常所说的透视效果,这里我采用的是云通道和云背景以不同的速度流动,制造一种飞行的透视效果。

云我采用的是贴图的方式呈现的,但是仅仅是贴图会遮挡天空和飞机,非常影响飞机飞行的观感,所以我开启了相应图元的 transparent 和 opacity ,云背景和云通道设置不同的透明度,不仅增加了层次感,还会让人产生云朵从眼前飘过的错觉。

云通道采用的是 ht.Polyline 类型,通道缩放拉大了 Y 轴的比例,使云通道有更大的纵向空间,设置 reverse.flip 背拷贝使云通道内部也显示出贴图,仿佛让飞机置身于云海中穿梭;云背景采用 ht.Node 类型,只设置一个面显示充当云背景。

整体的云流动效果采用 offset 偏移实现,改变相应图元或相应图元面的贴图偏移量来达到飞机云中穿行的效果, 代码如下:
 

var i = 1, 
    p = 0;
setInterval(() => {
    i -= 0.1; p += 0.005;
    clouds.s('shape3d.uv.offset', [i, 0]);
    cloudBackground.s('all.uv.offset', [p, 0]);
}, 100);

升降颠簸效果

虽然达到了飞机云中穿行的效果,但是如果飞机只是直直的飞行,那也会降低飞行的实感,相信坐过飞机的朋友肯定都遇到过因气流产生的颠簸,也经常感受到飞机飞行途中的爬升和下降,这其实是因为飞机的航线并不是一直固定在一个高度上,有时会爬升有时会下降,所以我就用 ht-animation.js HT 动画扩展插件去实现飞机颠簸效果,代码如下:

dm.enableAnimation(20);
plane.setAnimation({
    back1: {
        from: 0,
        to: 160,
        easing: 'Cubic.easeInOut',
        duration: 8000,
        next: "up1",
        onUpdate: function (value) {
            value = parseInt(value);
            var p3 = this.p3();
            this.p3(value, p3[1], p3[2]);
        }
    },
    //...省略相似
    start: ["back1"]
});

球扇形视角限制

飞行效果完善之后,这时我就遇到了一个比较棘手的问题,因为实际上虽然看着飞机是在云海中穿梭,但是仅仅是在通道中飞行,背景其实也只是平面贴图,所以当视角到达某种程度的时候就会有强烈的违和感和不真实感,就需要一个视角限制,使视角的调整刚刚好在一个范围内。

视角限制的话一般是限制 g3d 的 eye 和 center ,不太了解的朋友可以去看 hightopo 官网中的 3d 手册,里面有详细的说明,这里我就不再赘述了;因为视角范围的关系,所以我决定固定 center 的位置,代码如下:
 

g3d.addPropertyChangeListener(e => {
    // 固定中心点
    if (e.property === 'center') {
        e.newValue[0] = center[0];
        e.newValue[1] = center[1];
        e.newValue[2] = center[2];
    }
}

然后再把 eye 限制在某一个范围内就大功告成了,然而这里却并不是那么简单,最开始我把 eye 限制在一个立方体的空间内,但交互效果很不理想,考虑到 g3d 默认交互中,鼠标拖拽平移视角变换时,实际上 eye 是在一个以 center 为球心的球面上运动的,所以我决定从这个球中挖出来一块作为 eye 的限制空间,也就是球扇形,不太理解的朋友可以参考这个图:

HTML5 WebGL 实现民航客机飞行监控系统
 

球扇形视角限制,一共需要三个参数,分别是中心参考轴、中心轴和外边所成角度、所在球限制半径,其中中心参考轴可根据初始 eye 和 center 的连接延长线确定,所在球限制半径又分最大限制和最小限制,代码如下:

 

function limitEye(g3d, eye, center, options) {
    var limitMaxL   = options.limitMaxL,
        limitMinL   = options.limitMinL,
        limitA      = options.limitA;

    g3d.addPropertyChangeListener(e => {
        // 固定中心点
        if (e.property === 'center') {
            e.newValue[0] = center[0];
            e.newValue[1] = center[1];
            e.newValue[2] = center[2];
        }
        // 限制视角
        if (e.property === 'eye') {
            var newEyeV = new ht.Math.Vector3(e.newValue),
                centerV = new ht.Math.Vector3(center),
                refEyeV = new ht.Math.Vector3(eye),
                refVector = refEyeV.clone().sub(centerV),
                newVector = newEyeV.clone().sub(centerV);

            if (centerV.distanceTo(newEyeV) > limitMaxL) {
                newVector.setLength(limitMaxL);
                e.newValue[0] = newVector.x;
                e.newValue[1] = newVector.y;
                e.newValue[2] = newVector.z;
            }
            if (centerV.distanceTo(newEyeV) < limitMinL) {
                newVector.setLength(limitMinL);
                e.newValue[0] = newVector.x;
                e.newValue[1] = newVector.y;
                e.newValue[2] = newVector.z;
            }
            if (newVector.angleTo(refVector) > limitA) {
                var oldLength = newVector.length(),
                    oldAngle  = newVector.angleTo(refVector),
                    refLength = oldLength * Math.cos(oldAngle),
                    vertVector,
                    realVector,
                    realEye;

                refVector.setLength(refLength);

                newEyeV = newVector.clone().add(centerV);
                refEyeV = refVector.clone().add(centerV);
                vertVector = newEyeV.clone().sub(refEyeV);
                vertLength = refLength * Math.tan(limitA);

                vertVector.setLength(vertLength);

                realVector = vertVector.clone().add(refEyeV).sub(centerV);

                realVector.setLength(oldLength);

                realEye = realVector.clone().add(centerV);

                // 防止移动角度大于 180 度,视角反转
                if (oldAngle > Math.PI / 2) {
                    realEye.negate();
                }

                e.newValue[0] = realEye.x;
                e.newValue[1] = realEye.y;
                e.newValue[2] = realEye.z;
            }  
        }
    })
}

HTML5 WebGL 实现民航客机飞行监控系统

飞机监控系统

当然作为监控系统,自然要有监控了,增加右下角的小地图,并提供三种模式,分别是聚焦飞机,聚焦飞行轨迹和聚焦地图,并根据飞机的飞行方向控制飞行轨迹的流动效果,其中聚焦飞机会跟随飞机移动进行 fitData ,使飞机一直处于小地图的中心,代码如下:

 

var fitFlowP = function (e) {
    if (e.property === 'position' && e.data === plane) {
        mapGV.fitData(plane, false);
    }
};
buttonP.s({
    'interactive': true,
    'onClick': function (event, data, view, point, width, height) {
        map.a('fitDataTag', 'plane2D');
        mapGV.fitData(plane, false);
        mapDM.md(fitFlowP);
    }
});
buttonL.s({
    'interactive': true,
    'onClick': function (event, data, view, point, width, height) {
        mapDM.umd(fitFlowP);
        map.a('fitDataTag', 'flyLine');
        mapGV.fitData(flyLine, false);
    }
});
// ...省略

HTML5 WebGL 实现民航客机飞行监控系统

增加鼠标移到飞机相应位置进行名称的提示、双击后显示飞机相应位置的信息面板并将视角聚焦到面板上、点击飞机任意地方切换回飞机飞行模式等效果。

HTML5 WebGL 实现民航客机飞行监控系统
 

左侧增加监控面板替代上面提到的双击相应位置这步操作直接聚焦到相应位置的信息面板上,这里按钮开启了交互并添加了相应的交互逻辑,代码如下:

 

button_JC.s({
    'interactive': true,
    'onClick': function (event, data, view, point, width, height) {
        event.preventDefault();
        let g3d = G.g3d,
            g3dDM = G.g3d.dm();
        g3d.fireInteractorEvent({
            kind: 'doubleClickData',
            data: g3dDM.getDataByTag(data.getTag())
        })
    }
});
//...省略

HTML5 WebGL 实现民航客机飞行监控系统

天空渲染效果

既然是监控系统肯定是 24 小时无差别的监控,这就涉及到一个问题,我总不可能半夜的时候飞机也从瓦蓝瓦蓝的天空上飞过,这就很欠缺真实性了,所以要有一个天空从亮到暗再从暗到亮的过程,这个过程我暂定到 06:00-06:30 和19:00-19:30 这两个时间段。

天空采用的是 shape3d : 'sphere' 球形,包裹整个场景,然后使用 reverse.flip 背拷贝 和 blend 染色,之后天空就可以渲染成我想要的颜色,如果按照时间改变天空明暗只要改变染色值就可以了。

但是由于白天和晚上光照情况的不同,云反射光的强度也不同,就导致了白天和晚上云的差异,所以也要调整云道和云背景的贴图的 opacity 透明度,晚间更为透明度,代码如下:

if ((hour > 6 && hour < 19) || (hour == 6 && minutes >= 30)) {
    timePane && timePane.a({
        'morning.visible': false,
        'day.visible': true,
        'dusk.visible': false,
        'night.visible': false,
        'day.opacity': 1
    })
    skyBox.s({
        "shape3d.blend": 'rgb(127, 200, 240)',
    })
    cloudBackground.s({
        "back.opacity": 0.7,
    })
    clouds.s({
        "shape3d.opacity": 0.7,
    })
} else if ((hour < 6 || hour > 19) || (hour == 19 && minutes >= 30)) {
//...省略
} else if (hour == 6 && minutes < 15 ) {
//...省略
} else if (hour == 6 && minutes >= 15 && minutes < 30) {
//...省略
} else if (hour == 19 && minutes < 15) {
//...省略
} else if (hour == 19 && minutes >= 15 && minutes < 30) {
//...省略
}

HTML5 WebGL 实现民航客机飞行监控系统

这里我还增加了对右上角时间面板时间状态图标的支持,并增加了图标切换时的渐隐渐显效果,同时给时间面板状态图标位置增加了点击切换到下一时间状态的功能。

为了演示效果我增加了时间倍速按钮,下图是 500 倍时间流速下的变化情况:

HTML5 WebGL 实现民航客机飞行监控系统
 

总结

通过这个 demo ,我发现生活中有很多没有被人所注意到的细节都存在数据可视化的可能,在这个大数据的时代更多的可能性值得被人发掘出来,不要错个身边每一个值得数据可视化的细节,这样不仅可以更好的挖掘 HT for Web 的潜力,也可以加强自身身为一个程序员的综合素质。

HTML / CSS 相关文章推荐
css3 border旋转时的动画应用
Jan 22 HTML / CSS
浅析CSS3 中的 transition,transform,translate之间区别和作用
Mar 26 HTML / CSS
html5使用Canvas绘图的使用方法
Nov 21 HTML / CSS
canvas烟花特效锦集
Jan 17 HTML / CSS
HTML5 自动聚焦(autofocus)属性使用介绍
Aug 07 HTML / CSS
html5桌面通知(Web Notifications)实例解析
Jul 07 HTML / CSS
HTML5 source标签:媒介元素定义媒介资源
Jan 29 HTML / CSS
html5视频媒体标签video的使用方法及完整参数说明详解
Sep 27 HTML / CSS
HTML5 3D旋转相册的实现示例
Dec 03 HTML / CSS
HTML5+CSS设置浮动却没有动反而在中间且错行的问题
May 26 HTML / CSS
html5实现输入框fixed定位在屏幕最底部兼容性
Jul 03 HTML / CSS
HTML5逐步分析实现拖放功能的方法
Sep 30 HTML / CSS
关于前端上传文件全面基础扫盲贴(入门)
Aug 01 #HTML / CSS
把富文本的回车转为br标签
Aug 09 #HTML / CSS
浅谈cookie和localStorage那些事
Aug 27 #HTML / CSS
HTML5实时语音通话聊天MP3压缩传输3KB每秒
Aug 28 #HTML / CSS
详解基于 Canvas 手撸一个六边形能力图
Sep 02 #HTML / CSS
html+css实现自定义图片上传按钮功能
Sep 04 #HTML / CSS
Html5 Canvas 实现一个“刮刮乐”游戏
Sep 05 #HTML / CSS
You might like
PHP中ADODB类详解
2008/03/25 PHP
php使用qr生成二维码的示例分享
2014/01/20 PHP
PHP实现抓取HTTPS内容
2014/12/01 PHP
php树型类实例
2014/12/05 PHP
PHP回溯法解决0-1背包问题实例分析
2015/03/23 PHP
PHP Streams(流)详细介绍及使用
2015/05/12 PHP
JS 对输入框进行限制(常用的都有)
2013/07/30 Javascript
如何判断鼠标是否在DIV的区域内
2013/11/13 Javascript
解析Javascript中大括号“{}”的多义性
2013/12/02 Javascript
兼容主流浏览器的JS复制内容到剪贴板
2014/12/12 Javascript
jQuery通过扩展实现抖动效果的方法
2015/03/11 Javascript
JavaScript检查数字是否为整数或浮点数的方法
2015/06/09 Javascript
AngularJS使用angular-formly进行表单验证
2015/12/27 Javascript
如何用JS判断两个数字的大小
2016/07/21 Javascript
jquery实现多次上传同一张图片
2017/01/09 Javascript
jQuery选择器特殊字符与属性空格问题
2017/08/14 jQuery
JavaScript中的垃圾回收与内存泄漏示例详解
2019/05/02 Javascript
浅谈Node 异步IO和事件循环
2019/05/05 Javascript
JavaScript动态添加数据到表单并提交的几种方式
2019/06/26 Javascript
微信小程序绘制图片发送朋友圈
2019/07/25 Javascript
解决layui 三级联动下拉框更新时回显的问题
2019/09/03 Javascript
Python全局变量用法实例分析
2016/07/19 Python
flask + pymysql操作Mysql数据库的实例
2017/11/13 Python
django-初始配置(纯手写)详解
2019/07/30 Python
python实现多进程按序号批量修改文件名的方法示例
2019/12/30 Python
Python3自动生成MySQL数据字典的markdown文本的实现
2020/05/07 Python
利用Python的folium包绘制城市道路图的实现示例
2020/08/24 Python
使用py-spy解决scrapy卡死的问题方法
2020/09/29 Python
如何使用scrapy中的ItemLoader提取数据
2020/09/30 Python
好的自荐信包括什么内容
2013/11/07 职场文书
歌唱比赛获奖感言
2014/01/21 职场文书
机关道德讲堂实施方案
2014/03/15 职场文书
群众路线教师自我剖析材料
2014/09/29 职场文书
敬老院义诊活动总结
2015/05/07 职场文书
2015初中团支部工作总结
2015/07/21 职场文书
Springboot集成kafka高级应用实战分享
2022/08/14 Java/Android