3D roll and pitch indicator for HUD on Three.js

Browser games with 3D graphics have been around for a long time. There are also simulators of various vehicles, where the player needs to control the spatial position of the controlled object.







The article " An indicator of an artificial horizon on HTML5 canvas " presents an indicator code with a volumetric layout of a managed object based on the invention of A.P. Plentsov and N.A. Zakonovoy. ...



One of the advantages of the idea of ​​an indicator with a volumetric layout is its effectiveness. This time, an unusual artificial horizon visualization format will be adapted for systems augmented reality .



HUD vs HDD
, , head down display (HDD). HDD : , , .



( head up display HUD – Β« Β»), .



. :





HUD design features



Supplementing the observed reality with instrumental information is noticeably different from the usual indication of parameter values. The specificity of the task is reflected in the visual design of the HUD . In the most complex and critical systems ( for example , at the workplace of an airliner pilot), as a rule, a monochrome green indication is used in a "outline" design.



The minimalist design of the HUD is the answer to a set of conflicting system requirements. For example, the contour elements can have a sufficient angular dimension for the operator to read without obstructing the view of the external space.



Solution requirements



Let's define the key provisions of the assignment for the development of an artificial horizon indicator class:



1. The class constructor must have the following arguments:



  • the size of the face of the indicator;
  • limit displayed roll value;
  • limit displayed pitch value.


2. The display limits of each angle are determined by one value, which must not be exceeded by the absolute value of the displayed value. Limit values ​​cannot exceed 90 degrees.



3. The pitch scale should have seven numeric marks for the angle in degrees. The scale of the scale should be optimized when instantiating the object, the interval of the displayed values ​​should be minimal if the following conditions are met:



  • upper and lower marks are multiples of 30;
  • the maximum value of the pitch angle passed to the constructor does not go beyond the scale, including when multiplied by -1.








4. The roll scale should have marks in 30 degree increments around the entire circumference of the dial, regardless of the maximum roll angle reported to the designer. The roll scale marks should be displayed taking into account the pitch position of the layout, that is, the dial should rotate in the plane of symmetry of the workplace by the pitch angle around the axis passing through the center of the dial.







5. The model of the vehicle should be made in the form of a flat figure in the shape of an arrow. The ratio of the length of the layout to its width should ensure the rational use of the screen area. For example, if the pitch scale is limited to 90 degrees, then the length of the layout should correspond to about half of its width. When the scale is limited to 30 degrees, a significant proportion of the screen height is no longer used, as shown on the right side of the diagram.







To properly scale the scale with a smaller spacing, you need to change the proportions of the layout.







6. The class must have an update function that accepts the current values ​​of the roll and pitch angles.







7. The indicator should be green and outline. The number of indicator elements should be as small as possible, it is necessary to ensure that the overview of the background animation is preserved.



Result



You can evaluate the resulting indicator interactively on github pages .



The object in this example always moves strictly in the direction of its longitudinal axis. It is possible to set the values ​​of movement speed, roll angles and pitch. The movement is carried out only in the vertical plane, since the value of the heading angle is constant.



Indicator code



The artificial horizon indication code is shown below. The Attitude class uses the three.js library .



Attitude class code
class Attitude {
    constructor(camera, scene, radius, maxPitch, maxRoll) {
        //:
        //  30        :
        if (maxPitch > 90) maxPitch = 90;
        this.maxPitch = maxPitch;
        maxPitch /= 30;
        maxPitch = Math.ceil(maxPitch) * 30;

        //:
        if (maxRoll > 90) maxRoll = 90;
        this.maxRoll = maxRoll;

        //  :
        let skeletonLength = radius / Math.sin(maxPitch * Math.PI / 180);
        //  :
        let geometry = new THREE.Geometry();
        geometry.vertices.push(new THREE.Vector3(0, 0, -skeletonLength / 4));
        geometry.vertices.push(new THREE.Vector3(-radius, 0, 0));
        geometry.vertices.push(new THREE.Vector3(0, 0, -skeletonLength));
        geometry.vertices.push(new THREE.Vector3(radius, 0, 0));
        geometry.vertices.push(new THREE.Vector3(0, 0, -skeletonLength / 4)); //  
        // :
        let material = new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 1 });
        //  :
        this.skeleton = new THREE.Line(geometry, material);
        scene.add(this.skeleton);

        //  :
        let pitchScaleStep = maxPitch / 3;

        let textLabelsPos = [];//   
        for (let i = 0; i < 7; i++) {
            let lineGeometry = new THREE.Geometry();

            //     :
            let leftPoint = new THREE.Vector3(-radius / 10,
                skeletonLength * Math.sin((maxPitch - pitchScaleStep * i) * Math.PI / 180),
                -skeletonLength * Math.cos((maxPitch - pitchScaleStep * i) * Math.PI / 180));
            let rightPoint = new THREE.Vector3();
            rightPoint.copy(leftPoint);
            rightPoint.x += (radius / 5);
            // :
            lineGeometry.vertices.push(leftPoint);
            lineGeometry.vertices.push(rightPoint);
            let line = new THREE.Line(lineGeometry, material);
            scene.add(line);
            //  
            let textPos = new THREE.Vector3();
            textPos.copy(leftPoint);
            textLabelsPos.push(textPos);
        }

        //  :
        let rollScaleStep = 30;
        this.rollLines = [];
        for (let i = 0; i < 12; i++) {
            if (i != 3 && i != 9) {//     
                let lineGeometry = new THREE.Geometry();
                //  :
                lineGeometry.vertices.push(new THREE.Vector3(-Math.cos(
                    i * rollScaleStep * Math.PI / 180) * radius * 1.1,
                    Math.sin(i * rollScaleStep * Math.PI / 180) * radius * 1.1,
                    0));
                lineGeometry.vertices.push(new THREE.Vector3(-Math.cos(
                    i * rollScaleStep * Math.PI / 180) * radius * 0.9,
                    Math.sin(i * rollScaleStep * Math.PI / 180) * radius * 0.9,
                    0));

                this.rollLines.push(new THREE.Line(lineGeometry, material));
                scene.add(this.rollLines[this.rollLines.length - 1]);
            }
        }

        // :
        for (let i = 0; i < 7; i++) {
            let labelText = document.createElement('div');
            labelText.style.position = 'absolute';
            labelText.style.width = 100;
            labelText.style.height = 100;
            labelText.style.color = "Lime";
            labelText.style.fontSize = window.innerHeight / 35 + "px";
            labelText.innerHTML = Math.abs(maxPitch - pitchScaleStep * i);

            let position3D = textLabelsPos[i];
            let position2D = to2D(position3D);

            labelText.style.top = (position2D.y) * 100 / window.innerHeight - 2 + '%';
            labelText.style.left = (position2D.x) * 100 / window.innerWidth - 4 + '%';
            document.body.appendChild(labelText);
        }

        function to2D(pos) {
            let vector = pos.project(camera);
            vector.x = window.innerWidth * (vector.x + 1) / 2;
            vector.y = -window.innerHeight * (vector.y - 1) / 2;
            return vector;
        }

    }

    update(roll, pitch) {
        //   :
        if (pitch > this.maxPitch) pitch = this.maxPitch;
        if (pitch < -this.maxPitch) pitch = -this.maxPitch;

        if (roll > this.maxRoll) roll = this.maxRoll;
        if (roll < -this.maxRoll) roll = -this.maxRoll;

        //   ,      
        this.skeleton.rotation.z = -roll * Math.PI / 180;
        this.skeleton.rotation.x = pitch * Math.PI / 180;

        //   :
        let marksNum = this.rollLines.length;
        for (let i = 0; i < marksNum; i++)
            this.rollLines[i].rotation.x = pitch * Math.PI / 180;
    }
}

      
      







Parsing the code
, . XOZ, OZ, z.



YOZ. z.



Attitude . . , , .



constructor(camera, scene, radius, maxPitch, maxRoll){ 
      
      





( to2D()), – add().



. . 3 .



 if (maxPitch > 90) maxPitch = 90;
        this.maxPitch = maxPitch;
        maxPitch /= 30;
        maxPitch = Math.ceil(maxPitch) * 30;
      
      





30, 60 90 . - .



let skeletonLength = radius / Math.sin(maxPitch * Math.PI / 180);
      
      





radius , skeletonLength maxPitch: , . , maxPitch.



, . , .



, .



 let geometry = new THREE.Geometry();
        geometry.vertices.push(new THREE.Vector3(0, 0, -skeletonLength / 4));
        geometry.vertices.push(new THREE.Vector3(-radius, 0, 0));
        geometry.vertices.push(new THREE.Vector3(0, 0, -skeletonLength));
        geometry.vertices.push(new THREE.Vector3(radius, 0, 0));
        geometry.vertices.push(new THREE.Vector3(0, 0, -skeletonLength / 4));
        let material = new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 1 });
        this.skeleton = new THREE.Line(geometry, material);
        scene.add(this.skeleton);
      
      





, . .



three.js . :



1. , update(), , , . . , – .



2. , ( ), .



update() :



  • ;
  • .


html. 3D .



Disadvantages of the indicator



A quick acquaintance with the interactive demonstration is enough to notice the difficulties in reading readings at large absolute angles:



  • the beginning of the decline in the roll indication quality corresponds to the pitch angle of 75-80 degrees, at which the roll scale becomes noticeably compressed;
  • the beginning of a decrease in the quality of indication of small values ​​of the pitch angle corresponds to the values ​​of the roll angle of 70-75 degrees, at which the silhouette of the model loses its sweep;
  • indication of the inverted position of the object in the presented solution is excluded in principle.


It should be noted that there is no artificial horizon indication that works perfectly in any spatial position of the vehicle. The presented solution can be considered suitable for use on maneuvers of moderate intensity.



All Articles