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之背景尺寸Background-size使用介绍
Oct 14 HTML / CSS
HTML5 Canvas 破碎重组的视频特效的示例代码
Sep 24 HTML / CSS
X/HTML5 和 XHTML2
Oct 17 HTML / CSS
html5 worker 实例(一) 为什么测试不到效果
Jun 24 HTML / CSS
使用HTML5 Canvas为图片填充颜色和纹理的教程
Mar 21 HTML / CSS
HTML5移动端开发中的Viewport标签及相关CSS用法解析
Apr 15 HTML / CSS
HTML5新特性之type=file文件上传功能
Feb 02 HTML / CSS
H5最强接口之canvas实现动态图形功能
May 31 HTML / CSS
详解HTML5 Canvas标签及基本使用
Jan 10 HTML / CSS
canvas简单连线动画的实现代码
Feb 04 HTML / CSS
html5视频常用API接口的实战示例
Mar 20 HTML / CSS
html5表单的required属性使用
Jul 07 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
基于mysql的论坛(6)
2006/10/09 PHP
PHP中获取变量的变量名的一段代码的bug分析
2011/07/07 PHP
thinkphp使用literal防止模板标签被解析的方法
2014/11/22 PHP
php实现的一个简单json rpc框架实例
2015/03/30 PHP
PHP基于cookie与session统计网站访问量并输出显示的方法
2016/01/15 PHP
php实现的SSO单点登录系统接入功能示例分析
2016/10/12 PHP
php 中的信号处理操作实例详解
2020/03/04 PHP
javascript让setInteval里的函数参数中的this指向特定的对象
2010/01/31 Javascript
jQuery.extend 函数详解
2012/02/03 Javascript
jQuery下通过replace字符串替换实现大小图片切换
2012/05/22 Javascript
在js(jquery)中获得文本框焦点和失去焦点的方法
2012/12/04 Javascript
js对文章内容进行分页示例代码
2014/03/05 Javascript
input:checkbox多选框实现单选效果跟radio一样
2014/06/16 Javascript
JavaScript使用concat连接数组的方法
2015/04/06 Javascript
JS+CSS实现美化的下拉列表框效果
2015/08/11 Javascript
javascript超过容器后显示省略号效果的方法(兼容一行或者多行)
2016/07/14 Javascript
ES6新特性之解构、参数、模块和记号用法示例
2017/04/01 Javascript
浅谈通过JS拦截 pushState和replaceState事件
2017/07/21 Javascript
js实现登录注册框手机号和验证码校验(前端部分)
2017/09/28 Javascript
基于jQuery解决ios10以上版本缩放问题
2017/11/03 jQuery
JS实现点击生成UUID的方法完整实例【基于jQuery】
2019/06/12 jQuery
Node登录权限验证token验证实现的方法示例
2020/05/25 Javascript
Python使用MySQLdb for Python操作数据库教程
2014/10/11 Python
pyqt5 删除layout中的所有widget方法
2019/06/25 Python
python实现桌面气泡提示功能
2019/07/29 Python
关于python导入模块import与常见的模块详解
2019/08/28 Python
关于初始种子自动选取的区域生长实例(python+opencv)
2020/01/16 Python
CSS3样式linear-gradient的使用实例
2017/01/16 HTML / CSS
Css3新特性应用之视觉效果实例
2016/12/12 HTML / CSS
次世代生活态度:Hypebeast
2018/07/05 全球购物
毕业生毕业总结的自我评价范文
2013/11/02 职场文书
社区道德讲堂实施方案
2014/03/21 职场文书
小学班主任培训方案
2014/06/04 职场文书
项目投资合作意向书
2014/07/29 职场文书
简单的个人租房协议书范本
2014/11/26 职场文书
用python基于appium模块开发一个自动收取能量的小助手
2021/09/25 Python