I was on a business trip a few days ago and saw the overhead monitoring panel on the plane. In addition to playing TV series and advertisements, it would also switch to a monitoring system for aircraft navigation from time to time. However, the entire monitoring system felt a little crude, so On a whim, I made an upgraded version of the monitoring system using HT for Web. The demo is pretty good, so I’d like to share it with you so that we can learn from each other.
demo
Implementation processWalking through the clouds effect
In order to achieve the effect of an airplane flying through the clouds, the first problem I encountered was the layering of the airplane's flight, which is commonly known as the perspective effect. Here I used the cloud channel and cloud background to flow at different speeds to create a A flying perspective effect.
I presented the clouds in the form of textures, but just the textures would block the sky and the aircraft, which would greatly affect the look and feel of the aircraft flying, so I turned on the transparent and opacity of the corresponding graphics elements, and set different transparency levels for the cloud background and cloud channel. Not only does it add a sense of layering, it also gives people the illusion of clouds drifting by in front of their eyes.
The cloud channel uses the ht.Polyline type. The channel scaling enlarges the proportion of the Y-axis, giving the cloud channel a larger vertical space. Setting the reverse.flip back copy allows the texture to be displayed inside the cloud channel, as if the aircraft is in the air. Traveling through the sea of clouds; the cloud background adopts the ht.Node type, and only one surface is set to be displayed as the cloud background.
The overall cloud flow effect is achieved using offset offset, and the texture offset of the corresponding primitive or corresponding primitive surface is changed to achieve the effect of an airplane traveling through clouds. The code is as follows:
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);
Lifting bump effect
Although it achieves the effect of an airplane flying through the clouds, if the airplane only flies straight, it will also reduce the real feeling of flying. I believe that friends who have flown on an airplane must have encountered turbulence caused by airflow, and often feel the turbulence caused by airplane flight. The climb and descent on the way is actually because the aircraft's route is not always fixed at a certain altitude. Sometimes it climbs and sometimes drops, so I used the ht-animation.js
HT animation extension plug-in to achieve the bumpy effect of the aircraft. The code is as follows :
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]); } }, //...Omit similar start: [back1]});Sphere sector viewing angle restrictions
After the flight effect was perfected, I encountered a more difficult problem, because although the plane was actually flying through the sea of clouds, it was only flying in the channel, and the background was actually just a flat texture, so when the perspective reached a certain When this level is reached, there will be a strong sense of dissonance and unreality, and a viewing angle limit is needed to make the adjustment of the viewing angle just within a certain range.
Viewing angle restrictions generally limit the eye and center of g3d. Friends who don’t know much about it can read the 3D manual on the hightopo official website, which has detailed instructions. I won’t go into details here; because of the viewing angle range, I decided To fix the position of center, the code is as follows:
g3d.addPropertyChangeListener(e => { // Fixed center point if (e.property === 'center') { e.newValue[0] = center[0]; e.newValue[1] = center[1]; e.newValue[2] = center[2]; }}
Then limit the eye to a certain range and you're done. However, it's not that simple here. At first, I limited the eye to a cubic space, but the interaction effect was not ideal. Considering that in the default interaction of g3d, the mouse When dragging and panning to change the perspective, the eye actually moves on a spherical surface with center as the center, so I decided to dig out a piece of restricted space for the eye from the ball, which is a spherical sector. For those who don’t understand well You can refer to this picture:
The spherical fan-shaped viewing angle limit requires a total of three parameters, namely the central reference axis, the angle between the central axis and the outer edge, and the limited radius of the ball. The central reference axis can be determined based on the extension line connecting the initial eye and center, and the limited radius of the ball is located. It is divided into maximum limit and minimum limit. The code is as follows:
function limitEye(g3d, eye, center, options) { var limitMaxL = options.limitMaxL, limitMinL = options.limitMinL, limitA = options.limitA; g3d.addPropertyChangeListener(e => { // Fixed center point if (e.property = == 'center') { e.newValue[0] = center[0]; e.newValue[1] = center[1]; e.newValue[2] = center[2]; } // Limit the viewing angle 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); // Prevent the movement angle from being greater than 180 degrees and reverse the perspective if (oldAngle > Math.PI / 2) { realEye.negate(); } e.newValue[0] = realEye.x; e.newValue[1] = realEye.y; e.newValue[2] = realEye.z; } } } )}aircraft monitoring system
Of course, as a monitoring system, it is natural to have monitoring. Add a small map in the lower right corner and provide three modes: focus on the aircraft, focus on the flight trajectory and focus on the map, and control the flow effect of the flight trajectory according to the flight direction of the aircraft. Among them Focusing on the aircraft will follow the movement of the aircraft and perform fitData so that the aircraft is always in the center of the mini-map. The code is as follows:
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); }});// ...omitted
Added prompts for moving the mouse to the corresponding position of the aircraft to name it, double-clicking to display the information panel of the corresponding position of the aircraft and focusing the perspective on the panel, clicking anywhere on the aircraft to switch back to the aircraft flight mode, and other effects.
Adding a monitoring panel on the left replaces the above-mentioned double-clicking of the corresponding position, which directly focuses on the information panel at the corresponding position. The button here enables interaction and adds corresponding interaction logic. The code is as follows:
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()) }) }});//...omittedSky rendering effect
Since it is a monitoring system, it must be monitored 24 hours a day without distinction. This involves a problem. It is impossible for me to fly over the blue sky in the middle of the night. This lacks authenticity, so there must be a The process of the sky from light to dark and then from dark to light, I tentatively set this process to the two time periods of 06:00-06:30 and 19:00-19:30.
The sky uses a shape3d: 'sphere' spherical shape to wrap the entire scene, and then use reverse.flip to back copy and blend dye. After that, the sky can be rendered into the color I want. If I want to change the light and shade of the sky according to time, I only need to change the dye value. .
However, due to the different lighting conditions between day and night, the intensity of cloud reflected light is also different, which leads to the difference between clouds during the day and night. Therefore, it is also necessary to adjust the opacity transparency of the cloud channel and cloud background textures, which are more transparent at night. The code is as follows :
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)) {//...omitted} else if (hour == 6 && minutes < 15 ) {//...omitted} else if (hour == 6 && minutes >= 15 && minutes < 30) {//...omitted} else if (hour == 19 && minutes < 15) { //...Omitted} else if (hour == 19 && minutes >= 15 && minutes < 30) {//...Omitted}
Here I also added support for the time status icon in the upper right corner of the time panel, and added a fade-in and fade-out effect when the icon is switched. At the same time, I added a click-to-switch to the next time status icon position for the time panel status icon.
In order to demonstrate the effect, I added a time doubling button. The following picture shows the changes at 500 times the time flow rate:
SummarizeThrough this demo, I found that there are many details in life that have not been noticed by people, and there is the possibility of data visualization. In this era of big data, more possibilities are worth exploring. Don’t miss every valuable data around you. Visualizing details can not only better tap the potential of HT for Web, but also strengthen one's overall quality as a programmer.