Indicateur 3D de roulis et de tangage pour HUD sur Three.js

Les jeux par navigateur avec des graphismes 3D existent depuis longtemps. Il existe également des simulateurs de divers véhicules, où le joueur doit contrôler la position spatiale de l'objet contrôlé.







L'article " Un indicateur d'un horizon artificiel sur toile HTML5 " présente un code indicateur avec une mise en page volumétrique d'un objet géré basé sur l' invention de A.P. Plentsov et N.A. Zakonovoy. ...



L'un des avantages de l'idée d'un indicateur avec une disposition volumétrique est son efficacité. Cette fois, un format de visualisation d'horizon artificiel inhabituel sera adapté pour les systèmes réalité augmentée .



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



( head up display HUD – « »), .



. :





Caractéristiques de conception du HUD



Compléter la réalité observée avec des informations instrumentales est sensiblement différent de l'indication habituelle des valeurs de paramètres. La spécificité de la tâche se reflète dans la conception visuelle du HUD . Dans les systèmes les plus complexes et les plus critiques ( par exemple , sur le lieu de travail d'un pilote d'avion de ligne), en règle générale, une indication verte monochrome est utilisée dans une conception de «contour».



La conception minimaliste du HUD est la réponse à un ensemble d'exigences système contradictoires. Par exemple, les éléments de contour peuvent avoir une dimension angulaire suffisante pour que l'opérateur puisse les lire sans obstruer la vue de l'espace extérieur.



Exigences de la solution



Définissons les principales dispositions de l'affectation pour le développement d'une classe d'indicateur d'horizon artificiel:



1. Le constructeur de classe doit avoir les arguments suivants:



  • la taille du visage de l'indicateur;
  • limite la valeur de roulis affichée;
  • la valeur de hauteur maximale affichée.


2. Les limites d'affichage de chaque angle sont déterminées par une valeur, qui ne doit pas être dépassée par la valeur absolue de la valeur affichée. Les valeurs limites ne peuvent pas dépasser 90 degrés.



3. L'échelle de hauteur doit avoir sept marques numériques pour l'angle en degrés. L'échelle de l'échelle doit être optimisée lors de l'instanciation de l'objet, l'intervalle des valeurs affichées doit être minimal si les conditions suivantes sont remplies:



  • les marques supérieures et inférieures sont des multiples de 30;
  • la valeur maximale de l'angle de tangage passé au constructeur ne dépasse pas l'échelle, y compris lorsqu'elle est multipliée par -1.








4. L'échelle de roulis doit avoir des marques par incréments de 30 degrés sur toute la circonférence du cadran, quel que soit l'angle de roulis maximal signalé au concepteur. Les marques d'échelle de roulis doivent être affichées en tenant compte de la position de pas de la disposition, c'est-à-dire que le cadran doit tourner dans le plan de symétrie du lieu de travail de l'angle de pas autour de l'axe passant par le centre du cadran.







5. Le modèle du véhicule doit être réalisé sous la forme d'une figure plate en forme de flèche. Le rapport entre la longueur de la mise en page et sa largeur doit garantir une utilisation rationnelle de la surface de l'écran. Par exemple, si l'échelle de hauteur est limitée à 90 degrés, la mise en page doit avoir environ la moitié de sa largeur. Lorsque l'échelle est limitée à 30 degrés, une proportion importante de la hauteur de l'écran n'est plus utilisée, comme indiqué sur le côté droit du diagramme.







Pour mettre correctement l'échelle à l'échelle avec un espacement plus petit, vous devez modifier les proportions de la mise en page.







6. La classe doit avoir une fonction de mise à jour qui accepte les valeurs actuelles des angles de roulis et de tangage.







7. L'indicateur doit être vert et contour. Le nombre d'éléments indicateurs doit être aussi petit que possible, il est nécessaire de s'assurer que la vue d'ensemble de l'animation d'arrière-plan est préservée.



Résultat



Vous pouvez évaluer l'indicateur résultant de manière interactive sur les pages github .



L'objet dans cet exemple se déplace toujours strictement dans la direction de son axe longitudinal. Il est possible de régler les valeurs de la vitesse de déplacement, des angles de roulis et du tangage. Le déplacement s'effectue uniquement dans le plan vertical, car la valeur de l'angle de cap est constante.



Code indicateur



Le code d'indication d'horizon artificiel est indiqué ci-dessous. La classe Attitude utilise la bibliothèque three.js .



Code de classe d'attitude
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;
    }
}

      
      







Analyser le 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 .



Inconvénients de l'indicateur



Une rapide connaissance de la démonstration interactive suffit pour remarquer les difficultés de lecture des lectures sous de grands angles absolus:



  • le début de la baisse de la qualité de l'indication de roulis correspond à l'angle de tangage de 75 à 80 degrés, auquel l'échelle de roulis est sensiblement comprimée;
  • le début d'une diminution de la qualité de l'indication de petites valeurs de l'angle de tangage correspond aux valeurs de l'angle de roulis de 70 à 75 degrés, auxquelles la silhouette du modèle perd son balayage;
  • l'indication de la position inversée de l'objet dans la solution présentée est exclue en principe.


Il convient de noter qu'il n'y a pas d'indication d'horizon artificiel qui fonctionne parfaitement dans n'importe quelle position spatiale du véhicule. La solution présentée peut être considérée comme appropriée pour une utilisation sur des manœuvres d'intensité modérée.



All Articles