Der Artikel " Ein Indikator für einen künstlichen Horizont auf HTML5-Leinwand " enthält einen Indikatorcode mit einem volumetrischen Layout eines verwalteten Objekts, das auf der Erfindung von A. P. Plentsov und N. A. Zakonovoy basiert. ...
Einer der Vorteile der Idee eines Indikators mit volumetrischem Layout ist seine Wirksamkeit. Dieses Mal wird ein ungewöhnliches Visualisierungsformat für künstliche Horizonte für Systeme angepasst Augmented Reality .
HUD vs HDD
HUD-Designmerkmale
Die Ergänzung der beobachteten Realität durch instrumentelle Informationen unterscheidet sich deutlich von der üblichen Angabe von Parameterwerten. Die Spezifität der Aufgabe spiegelt sich in der visuellen Gestaltung des HUD wider . In den komplexesten und kritischsten Systemen ( zum Beispiel am Arbeitsplatz eines Verkehrsflugzeugpiloten) wird in der Regel eine monochrome grüne Anzeige in einem "Umriss" -Design verwendet.
Das minimalistische Design des HUD ist die Antwort auf eine Reihe widersprüchlicher Systemanforderungen. Beispielsweise können die Konturelemente eine ausreichende Winkelabmessung aufweisen, damit der Bediener lesen kann, ohne die Sicht auf den Außenraum zu beeinträchtigen.
Lösungsanforderungen
Definieren wir die wichtigsten Bestimmungen der Aufgabe für die Entwicklung einer Indikatorklasse für künstliche Horizonte:
1. Der Klassenkonstruktor muss die folgenden Argumente haben:
- die Größe der Fläche des Indikators;
- angezeigten Rollwert begrenzen;
- der maximal angezeigte Tonhöhenwert.
2. Die Anzeigegrenzen jedes Winkels werden durch einen Wert bestimmt, der vom absoluten Wert des angezeigten Werts nicht überschritten werden darf. Grenzwerte dürfen 90 Grad nicht überschreiten.
3. Die Tonhöhenskala sollte sieben numerische Markierungen für den Winkel in Grad haben. Die Skalierung der Skalierung sollte beim Instanziieren des Objekts optimiert werden. Das Intervall der angezeigten Werte sollte minimal sein, wenn die folgenden Bedingungen erfüllt sind:
- obere und untere Markierung sind Vielfache von 30;
- Der maximale Neigungswinkelwert, der an den Konstruktor übergeben wird, geht nicht über die Skala hinaus, auch wenn er mit -1 multipliziert wird.
4. Die Rollskala sollte Markierungen in Schritten von 30 Grad über den gesamten Umfang des Zifferblatts aufweisen, unabhängig vom maximalen Rollwinkel, der dem Konstrukteur gemeldet wurde. Die Rollenskalenmarkierungen sollten unter Berücksichtigung der Neigungsposition des Modells angezeigt werden, dh das Zifferblatt sollte in der Symmetrieebene des Arbeitsplatzes um den Neigungswinkel um die Achse gedreht werden, die durch die Mitte des Zifferblatts verläuft.
5. Das Modell des Fahrzeugs sollte in Form einer flachen Figur in Form eines Pfeils hergestellt werden. Das Verhältnis der Länge des Layouts zu seiner Breite sollte die rationelle Nutzung des Bildschirmbereichs gewährleisten. Wenn die Tonhöhenskala beispielsweise auf 90 Grad begrenzt ist, sollte das Layout etwa halb so breit sein. Wenn der Maßstab auf 30 Grad begrenzt ist, wird ein erheblicher Teil der Bildschirmhöhe nicht mehr verwendet, wie auf der rechten Seite des Diagramms gezeigt.
Um die Skalierung mit einem kleineren Abstand richtig zu skalieren, müssen Sie die Proportionen des Layouts ändern.
6. Die Klasse muss über eine Aktualisierungsfunktion verfügen, die die aktuellen Werte der Roll- und Nickwinkel akzeptiert.
7. Die Anzeige sollte grün und umrandet sein. Die Anzahl der Indikatorelemente sollte so gering wie möglich sein. Es ist darauf zu achten, dass die Übersicht über die Hintergrundanimation erhalten bleibt.
Ergebnis
Sie können den resultierenden Indikator interaktiv auf Github-Seiten auswerten .
Das Objekt in diesem Beispiel bewegt sich immer streng in Richtung seiner Längsachse. Es ist möglich, die Werte für Bewegungsgeschwindigkeit, Rollwinkel und Neigung einzustellen. Die Bewegung wird nur in der vertikalen Ebene ausgeführt, da der Wert des Steuerkurswinkels konstant ist.
Anzeigecode
Der Code für die Anzeige des künstlichen Horizonts ist unten dargestellt. Die Attitude- Klasse verwendet die Bibliothek three.js .
Einstellungsklassencode
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;
}
}
Code analysieren
, .
XOZ,
OZ,
z.
YOZ. z.
Attitude . . , , .
( to2D()), – add().
. . 3 .
30, 60 90 . - .
radius , skeletonLength maxPitch: , . , maxPitch.
, . , .
, .
, . .
three.js . :
1. , update(), , , . . , – .
2. , ( ), .
update() :
html. 3D .
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 .
Nachteile des Indikators
Eine schnelle Kenntnis der interaktiven Demonstration reicht aus, um die Schwierigkeiten beim Lesen von Messwerten in großen absoluten Winkeln zu bemerken:
- Der Beginn des Rückgangs der Rollanzeigequalität entspricht dem Nickwinkel von 75-80 Grad, bei dem die Rollskala merklich komprimiert wird.
- Der Beginn einer Abnahme der Qualität der Anzeige kleiner Werte des Nickwinkels entspricht den Werten des Rollwinkels von 70 bis 75 Grad, bei denen die Silhouette des Modells ihren Schwung verliert.
- Die Angabe der umgekehrten Position des Objekts in der vorgestellten Lösung ist grundsätzlich ausgeschlossen.
Es ist zu beachten, dass es keine künstliche Horizontanzeige gibt, die in jeder räumlichen Position des Fahrzeugs perfekt funktioniert. Die vorgestellte Lösung kann als geeignet für Manöver mittlerer Intensität angesehen werden.