WegGL 3D industrial Internet of things tunnel monitoring practice

Posted by brown2005 on Sat, 19 Oct 2019 12:22:58 +0200

Preface

It is very necessary to monitor the lane jam in the tunnel, the accident scene in the tunnel, display the current accident position in the tunnel and give prompt at the tunnel entrance. The main contents of this tunnel Demo include: lighting, fan, Lane indicator light, traffic signal light, information board, fire protection, fire alarm, crosswalk, wind direction indicator, microwave vehicle inspection, tunnel emergency exit control and accident simulation, etc.

Design sketch

http://www.hightopo.com/demo/tunnel2/index.html

All kinds of equipment in the above figure can be double clicked. At this time, the position of camera will move from the current position to the front of the double clicked equipment. The display board at the tunnel entrance will automatically rotate. In case of an accident, the contents of the display board will change from "speed limit 80, please turn on the lights" to "overtaking lane, two cars rear end, please slow down and slow down"; the sign above the escape channel in the middle of the two tunnels is OK. Click, click to switch to the blue-green activation state, the escape way doors on both sides will also open, click the indicator to turn gray, and the doors will be closed; there is also an accident scene simulation, double-click one of the transformers on both sides, an "accident scene icon" will appear in the tunnel, click this icon, a pop-up box will appear to display the accident, etc.

code implementation

Scene construction

The whole tunnel is drawn based on 3D scene. Let's see how to build 3D scene first:

dm = new ht.DataModel(); / / data container
g3d = new ht.graph3d.Graph3dView(dm);// 3d scene
g3d.addToDOM(); / / add the scene to the body

The addToDOM function in the above code is a package of functions that add components to the body. The definition is as follows:

;)

addToDOM = function(){

var self = this,
     view = self.getView(),//Get the underlying div of the component
     style = view.style;
document.body.appendChild(view);//Add the component's underlying div to the body
style.left = '0';//ht sets the position of all components to absolute by default.
style.right = '0';
style.top = '0';
style.bottom = '0';
window.addEventListener('resize', function () { self.iv(); }, false);//Window size change event, call refresh function

}

;)

JSON deserialization

The whole scene is exported from a file named tunnel 1.json. I just need to use the code to convert the contents of the JSON file into the parts I need:

ht.Default.xhrLoad('./scenes / tunnel 1.json', function(text) {//xhrLoad function is a function to load files asynchronously

var json = ht.Default.parse(text);//Convert the text in the json file to the content in the json format we need
dm.deserialize(json);//Deserialize the Data container, parse the corresponding Data object and add it to the Data container. This is equivalent to deserializing the ht.Node node generated in the json file into the Data container, so the Data container has this node.

});

Because xhrLoad function is an asynchronous load function, if dm data container calls its nodes directly before deserialization is completed, it will result in the result that data cannot be obtained. So generally speaking, I write some logic code in this function, or set timeout to the logic code to stagger the time difference.

First of all, because the data is stored in the dm data container (added by dm.add(node)), we can not only get the data through id, tag and other independent ways, but also through traversing the data container to get multiple elements. Due to the complexity of this scenario and the large number of models, I will be able to Batch The elements in batch are all in batch:

;)

dm.each(function(data) {

if (data.s('front.image') === 'assets/sos Telephone.png'){//Batch "phone"
    data.s('batch', 'sosBatch');
}
else if (data.s('all.color') === 'rgba(222,222,222,0.18)') {//Batch of escape routes (transparency also affects performance)
    data.s('batch', 'emergencyBatch');
}
else if (data.s('shape3d') === 'models/Tunnel/Camera.json' || data.s('shape3d') === 'models/Tunnel/Transverse hole.json' || data.s('shape3d') === 'models/Tunnel/Rolling shutter door.json') {
    if(!data.s('shape3d.blend'))//Some cameras are dyed, no batch
        data.s('batch', 'basicBatch');//Basic batch do nothing
}
else if (data.s('shape3d') === 'models/Large transformer/Transformer.json') {    
    data.s('batch', 'tileBatch');
    data.setToolTip('Click roam, double-click the icon of the accident site');
}
else if (data.getDisplayName() === 'ground') {
    data.s('3d.selectable', false);//Set tunnel "ground" unselectable
}
else if (data.s('shape3d') === 'models/Tunnel/Exhaust air.json') {
    data.s('batch', 'fanBatch');//The model of exhaust fan is relatively complex, so batch
}
else if (data.getDisplayName() === 'arrow') {//Arrow signs on both sides of the tunnel
    if (data.getTag() === 'arrowLeft') data.s('shape3d.image', 'displays/abc.png');
    else data.s('shape3d.image', 'displays/abc2.png');
    data.s({
        'shape3d': 'billboard',
        'shape3d.image.cache': true,//Cache. The cost of setting cache is to set invalideshape3dcachedimage.
        'shape3d.transparent': true //Set this value to make the aliasing on the image less obvious (if the image type is json, set shape3d.dynamic.transparent)
    });
    g3d.invalidateShape3dCachedImage(data);
}
else if (data.getTag() === 'board' || data.getTag() === 'board1') {//Information board at the entrance of the tunnel
    data.a('textRect', \[0, 2, 244, 46\]); //Business attribute, used to control the position of text \ [x,y,width,height \]
    data.a('limitText', 'Speed limit 80, please turn on the lights');//Business properties, setting text content
    var min = -245;
    var name = 'board' + data.getId();
    window\[name\] = setInterval(function() {
        circleFunc(data, window\[name\], min)//Set the text in the message board to scroll to the left and blink three times when all the text is displayed
    }, 100);
}

//Dynamically color the indicator board above the escape way
var infos = \['Pedestrian crossing 1', 'Pedestrian crossing 2', 'Pedestrian crossing 3', 'Pedestrian crossing 4', 'Vehicle crossing hole 1', 'Vehicle crossing hole 2', 'Vehicle crossing hole 3'\];
infos.forEach(function(info) {
    if(data.getDisplayName() === info) {
        data.a('emergencyColor', 'rgb(138, 138, 138)');
    }
});

infos = \['Lane indicator', 'Lane indicator 1', 'Lane indicator 2', 'Lane indicator 3'\];
infos.forEach(function(info) {
    if (data.getDisplayName() === info) {
        createBillboard(data, 'assets/Lane signal-too.png', 'assets/Lane signal-too.png', info)//Transform hexahedron to billboard type elements for performance
    }
});

});

;)

There is a tooltip text message set above. In 3d, to display the text message, you need to set the g3d.enabletotooltip() function. The default 3d component is to turn off this function.

Logic code

Information board scroll bar

I'll explain it directly according to the method mentioned in the above code. First, it's the function of circleFunc intelligence board text circulating movement. In this function, we use the business property limitText to set the text property in the intelligence board and textRect to set the text moving position property in the intelligence board:

;)

function circleFunc(data, timer, min) {/ / set the text in the message board to scroll to the left and blink three times when all the text is displayed.

var text = data.a('limitText');//Get the content of the current business property limitText
data.a('textRect', \[data.a('textRect')\[0\]-5, 2, 244, 46\]); //Set the coordinates and size of textRect text box of business property
if (parseInt(data.a('textRect')) <= parseInt(min)) {
    data.a('textRect', \[255, 2, 244, 46\]);
}
else if (data.a('textRect')\[0\] === 0) {
    clearInterval(timer);
    var index = 0;
    var testName = 'testTimer' + data.getId();//Multiple timers are set because more than one data can enter this function. If multiple data sets the same timer at the same time, only the last node will be animated. There are many other traps in the back. Please pay attention to them.
    window\[testName\] = setInterval(function() {
        index++;
        if(data.a('limitText') === '') {//If the text content in the information board is empty
            setTimeout(function() {
                data.a('limitText', text);//Set to incoming text value
            }, 100);
        }
        else {
            setTimeout(function() {
                data.a('limitText', ''); //If the text content in the information board is not empty, it is set to empty.
            }, 100);
        }

        if(index === 11) { //Repeat three times. 
            clearInterval(window\[testName\]);
            data.a('limitText', text);
        }
    }, 100);

    setTimeout(function() {
        timer = setInterval(function() {
            circleFunc(data, timer, min) //Callback function
        }, 100);
    }, 1500);
}

}

;)

Because WebGL has not low requirements for browsers, in order to adapt to the major browsers as much as possible, we will replace all the hexahedrons of "road indicator" ht.Node type with billboard type nodes, and the performance can be improved a lot.

http://www.hightopo.com

The method of setting billboard is very simple. Get the current hexahedral nodes, and then set these nodes:

;)

node.s({

'shape3d': 'billboard',
'shape3d.image': imageUrl,
'shape3d.image.cache': true

});
g3d.invalidateShape3dCachedImage(node); / / remember the cost of using shape3d.image.cache?

;)

Of course, because billboard can't display different images on both sides, it is just a "face", so we have to create another node in the node's position, displaying the picture on the back of this node, and exactly the same as the configuration of the node, but the position should be slightly offset.

Camera slow offset

Other animation parts are relatively simple, so I won't talk about it here. Here is an animation where the double-click node can move the line of sight from the current camera position to the position directly in front of the double-click node. I encapsulate two functions, setEye and setCenter, which are used to set the location of camera and the target location respectively:

;)

function setCenter(center, finish) {/ / set the target location

var c = g3d.getCenter().slice(0), //Get the current "target" position as an array, and the getCenter array will change in the process of eye movement, so we need to copy one first
    dx = center\[0\] - c\[0\], //Difference between current x-axis position and target position
    dy = center\[1\] - c\[1\],
    dz = center\[2\] - c\[2\];
// Start animation over 500ms
ht.Default.startAnim({
    duration: 500,
    action: function(v, t) {
        g3d.setCenter(\[ //Slowly move the target position from the current position to the set position
            c\[0\] + dx * v,
            c\[1\] + dy * v,
            c\[2\] + dz * v
        \]);
    }
});

};

function setEye(eye, finish) {/ / set the "eye" position

var e = g3d.getEye().slice(0),//Get the current "eye" position as an array, and getEye array will change in the process of eye movement, so we need to copy one first
    dx = eye\[0\] - e\[0\],
    dy = eye\[1\] - e\[1\],
    dz = eye\[2\] - e\[2\];

// Start animation over 500ms
ht.Default.startAnim({
    duration: 500,
    action: function(v, t) {//Slowly move the Camera position from the current position to the set position
        g3d.setEye(\[
            e\[0\] + dx * v,
            e\[1\] + dy * v,
            e\[2\] + dz * v
        \]);
    }
});

};

;)

Later, when we need to set, we will call these two functions directly and set the parameters as our target location. For example, for each model in my scene, because the rotation angles of each model corresponding to different viewing angles are also different, I can only find several typical angles of 0 °, 90 °, 180 ° and 360 °. So when drawing a 3D scene, I also try to set the rotation angle of the node as one of the four (and for our scene, basically only rotate on the y axis):

;)

var p3 = e.data.p3(), / / get the three-dimensional coordinates of the event object

s3 = e.data.s3(),//Get the 3D size of the event object
r3 = e.data.r3();//Get the 3D rotation value of the event object

setCenter(p3); / / set the target location to the 3D coordinate value of the current event object
If (R3 [1]! = = 0) {/ / if the y-axis rotation value of the node is not 0

if (parseFloat(r3\[1\].toFixed(5)) === parseFloat(-3.14159)) { //Floating point negative numbers have to be converted for ratio
    setEye(\[p3\[0\], p3\[1\]+s3\[1\], p3\[2\] * Math.abs(r3\[1\]*2.3/6)\]);//Set the target location of the camera
}
else if (parseFloat(r3\[1\].toFixed(4)) === parseFloat(-1.5708)) {
    setEye(\[p3\[0\] * Math.abs(r3\[1\]/1.8), p3\[1\]+s3\[1\], p3\[2\]\]);
}
else {
    setEye(\[p3\[0\] *r3\[1\], p3\[1\]+s3\[1\], p3\[2\]\]);
}

}
else {

setEye(\[p3\[0\], p3\[1\]+s3\[1\]*2, p3\[2\]+1000\]);

}

;)

Accident simulation site

Finally, let's talk about the simulated accident scene, which is close to the actual project. The operation process is as follows: double click the "transformer" - > a "accident scene" icon will appear in the middle of the tunnel -- > click the icon, pop up a dialog box, display the current accident information -- > click OK, the lights before the accident scene will be displayed as red ×, and the text on the information board at the tunnel entrance will be displayed as "overtaking lane, two cars rear end, please slow down" - > double click again Secondary "transformer", the scene restores the state before the accident.

In HT, you can monitor the interaction process through graph3dview ා addinteractiorlistener (mi in short):

;)

g3d.addInteractorListener(function(e) {

if(e.kind === 'doubleClickData') {
    if (e.data.getTag() === 'jam') return;//There is an "accident" icon node
    if (e.data.s('shape3d') === 'models/Large transformer/Transformer.json') {//If the double-click object is a transformer
        index++;
        var jam = dm.getDataByTag('jam');//Get the accident icon node object by uniquely identifying the tag
        if(index === 1){
            var jam = dm.getDataByTag('jam');
            jam.s({
                '3d.visible': true,//Set nodes visible on 3d
                'shape3d': 'billboard',//Set the node to billboard type
                'shape3d.image': 'assets/traffic accident.png', //Set the display picture of billboard
                'shape3d.image.cache': true,//Set whether billboard pictures are cached
                'shape3d.autorotate': true,//Always facing the camera
                'shape3d.fixSizeOnScreen': \[30, 30\],//Keep the original size of the picture by default. Set it to array mode to set the size of the picture displayed on the interface.
            });
            g3d.invalidateShape3dCachedImage(jam);//The cost of cache is that the node needs to set this function
         }
         else {
             jam.s({
                 '3d.visible': false//The second double-click of the transformer will restore everything to the state before the "accident"
            });
            dm.each(function(data) {
                var p3 = data.p3();
                if ((p3\[2\] < jam.p3()\[2\]) && data.getDisplayName() === 'Lane indicator 1') {
                    data.s('shape3d.image', 'assets/Lane signal-too.png');
                }
                if(data.getTag() === 'board1') {
                    data.a('limitText', 'Speed limit 80, please turn on the lights');
                }
            });
            index = 0;
        }
                    
    }
}

});

;)

Since the "accident" node icon appears, then click the icon to display the "accident information pop-up box". The listening event is also in mi (addinteractionlistener), but this time we are listening to the click event. We know that a click event will be triggered when listening to the double-click event. To avoid this situation, I have made a delay in the click event:

;)

else if (e.kind === 'clickData') {/ / click the element

timer = setTimeout(function() {
    clearTimeout(timer);
    if (e.data.getTag() === 'jam') {//If it is an "accident" icon node
        createDialog(e.data);//Create a dialog
    }
}, 200);

}

;)

In the above double-click event, I don't have clearTimeout. I'm afraid that the order problem will cause trouble for you. Please remember to add it.

The pop-up box is as follows:

The pop-up box is composed of two ht.widget.FormPane forms. The form on the left has only one row, with the row height of 140. The form on the right is composed of five rows. Click OK, and the road indicator before the "accident" icon node will be replaced with a red x Icon:

;)

function createForm4(node, dialog) {/ / the form on the right of the pop-up box

var form = new ht.widget.FormPane();//Form component
form.setWidth(200);//Set the width of the form component
form.setHeight(200);//Set the height of the form component
var view = form.getView();//Get the underlying div of the form component 
document.body.appendChild(view);//Add form component to body

var infos = \[
    'Content of edit box: 2 sets',
    'The content of the edit box is: Bus-Passenger car',
    'Edit box content: no fire',
    'The content of the edit box is: overtaking lane'
\];
infos.forEach(function(info) {
    form.addRow(\[ //Add rows to the form
        info
    \], \[0.1\]);//The second parameter is row width, and values less than 1 are relative values.
});

form.addRow(\[
    {
        button: {//Add a line of "confirm" button
            label: 'confirm',
            onClicked: function() {//Button click event trigger
                dialog.hide();//Hide dialog
                dm.each(function(data) {
                    var p3 = data.p3();
                    if ((p3\[2\] < node.p3()\[2\]) && data.getDisplayName() === 'Lane indicator 1') {//Change the display picture of "Lane indicator" to red ×, here I judge whether "Lane indicator" is in front or behind according to the coordinates of "accident" icon node.
                        data.s('shape3d.image', 'assets/Lane signal-prohibit.png');
                    }
                    if(data.getTag() === 'board1') {//Replace the text on the information board of the tunnel portal
                        data.a('limitText', 'Overtaking lane, two cars rear end, please slow down');
                    }
                });
            }
        }
    }
\], \[0.1\]);
return form;

}

;)

Concluding remarks

The Demo of this industrial tunnel has been improved by me for several days, and there may still be some shortcomings. But generally speaking, I'm quite satisfied, and I may continue to improve later. I also have to rely on you to give me opinions and suggestions. I just hope that I can help others with my own efforts. In the whole Demo, I mainly encountered two problems: one is the problem of setting timer that I mentioned in the code. If multiple nodes use one timer at the same time, only the last node can show the timer effect; the other is the problem of getEye and getCenter, both of which are constantly changing, so it is necessary to copy a copy of data before counting. According to the transformation.

Topics: Javascript JSON less Attribute