- 1. Punkt und Vektor
- 2. Ebene
- 3. Gerade
- 4. Fazit
Mit diesem Artikel möchte ich meine Erfahrungen im Umgang mit der Computergeometrie teilen, die ich während der Arbeit an meiner Dissertation gesammelt habe. Nicht jeder weiß, dass Qt Creator Tools zum Arbeiten mit Geometrie enthält (insbesondere QVector3D), für die es kein detailliertes Handbuch in russischer Sprache gibt. Daher versuche ich hier, die notwendige Theorie und deren Umsetzung kurz zu skizzieren.
Punkt und Vektor
Um einen Punkt in Qt zu beschreiben, werden die Klassen QVector2D, QVector3D und QVector4D (nicht zu verwechseln mit der Klasse QVector) für zweidimensionale, dreidimensionale bzw. vierdimensionale Räume bereitgestellt. Tatsächlich handelt es sich dabei um Arrays aus zwei, drei und vier Float-Variablen mit einem Satz geometrischer Funktionen. Daher werde ich alle Beispiele nur für die nützlichsten QVector3D in der Praxis geben.
QVector3D v1(1,1,1) v2(1,1,1), v3(1.1f,2.5f,3.8f);
Im Beispiel sind 3 Vektoren gegeben. Die Klammern enthalten x-, y-, z-Koordinaten. Jede der Vektorkomponenten kann erhalten oder neu definiert werden.
v3.setX(0.5); v3.setY(0.6); v3.setZ(0.7); qDebug() << v3.x() << v3.y() << v3.z() << v3;
> 0,5 0,6 0,7 QVector3D(0,5, 0,6, 0,7)
Dieselben Klassen werden verwendet, um Vektoren zu beschreiben. Daher haben einige der Funktionen unterschiedliche Bedeutungen für Vektoren und Punkte. So ist beispielsweise die Normalisierung eine Operation, die einen gegebenen Vektor in einen Vektor der Einheitslänge umwandelt:
wobei die Länge (Modul) des Vektors ist. QVector3D hat dafür zwei Funktionen:
v1.normalize(); // функция, изменяющая переменные объект qDebug() << v1 << v2.normalized(); // функция, не переменные объект
> QVector3D(0.57735, 0.57735, 0.57735) QVector3D(0.57735, 0.57735, 0.57735)
Im Beispiel wurde der Vektor, der eine Längenwurzel von drei hatte, in einen Einheitsvektor umgerechnet (solche normierten Vektoren in der Geometrie sind für verschiedene geometrische Operationen notwendig). Dies lässt sich leicht mit der Funktion length() überprüfen, die die Länge des Vektors zurückgibt.
float L = v1.length(); // длина вектора или расстояние от точки до начала координат
Bei manchen Aufgaben interessiert uns nicht die Länge des Vektors, sondern sein Quadrat. Dafür steht die Funktion lengthSquared() zur Verfügung, die die Zahl der Berechnungen reduziert
float L2 = v1.lengthSquared();
Vor vielen Berechnungen muss überprüft werden, ob die verwendeten Vektoren nicht Null sind (alle Komponenten sind Null). Andernfalls ist das Ergebnis unzureichend (sogar Fehler sind möglich).
bool b = v1.isNull(); // проверка: является ли вектор нулевым?
Vektoren können addiert und subtrahiert werden (Addieren ihrer jeweiligen Koordinaten). Ein Vektor kann auch mit einer Zahl multipliziert oder dividiert werden (erhält einen Vektor, der n-mal größer oder kleiner in der Länge ist). Sie sollten mit der Multiplikationsoperation vorsichtiger sein, weil. der Ausdruck v1*v2 wird auf die Multiplikation der entsprechenden Koordinaten der Vektoren reduziert. Die Divisionsoperation v1/v2 funktioniert ähnlich. Die geometrische Interpretation der beiden vorherigen Operationen dehnt und schrumpft das Koordinatensystem entlang der entsprechenden Achsen um die in v2 angegebenen Werte. Die Funktionen dotProduct(v1, v2) und crossProduct(v1, v2) werden bereitgestellt, um die Skalar- und Vektorprodukte zu implementieren.
QVector3D v11(1,0,0), v12(0,1,0); qDebug() << v11*v12 << QVector3D::dotProduct(v11, v12) << QVector3D::crossProduct(v11, v12);
> QVector3D(0, 0, 0) 0 QVector3D(0, 0, 1)
Ich möchte Sie daran erinnern, dass das Skalarprodukt zweier Vektoren eine Zahl ist, die definiert ist als
und im Fall der Multiplikation von Normalenvektoren gleich dem Kosinus des Winkels zwischen ihnen ist.
Das Kreuzprodukt kann wie folgt berechnet werden
wobei i, j, k die Orte des kartesischen Koordinatensystems sind, direkte Klammern (hier und unten) sind die Determinante der Matrix. Das Ergebnis dieser Operation ist ein Vektor senkrecht zu der Ebene, in der zwei Faktorvektoren liegen, oder ein Nullvektor, wenn v1 und v2 kollinear sind. Der Modul des Vektorprodukts ist gleich dem Produkt der Module der Faktoren und dem Sinus des Winkels zwischen ihnen.
Vektoren können auf Gleichheit geprüft werden. Zwei Vektoren sind gleich, wenn alle ihre entsprechenden Koordinaten gleich sind. Es sollte jedoch beachtet werden, dass sich bei komplexen Berechnungen ein Fehler häufen kann. Daher haben die Entwickler die Möglichkeit eines „weichen“ Vergleichs vorgesehen.
v2 = v1 + QVector3D(0.000001f, 0.000001f, 0.000001f); qDebug() << (v1 == v2) << qFuzzyCompare(v1, v2);
> falsch wahr
Der Abstand zwischen zwei Punkten ist gegeben durch
eingebettet in die Funktion distanceToPoint(v2).
QVector3D v1(0,1,1), v2(1,0,1); qDebug() << v1.distanceToPoint(v2);
> 1.41421
Bei Vektoren ist die Bedeutung der letzten Operation die Länge des Vektors ihrer Differenz.
qDebug() << (v1-v2).length();
> 1.41421
Ebene
Jede Ebene kann durch drei paarweise nicht zusammenfallende Punkte T1, T2 und T3 definiert werden, die nicht auf derselben Geraden liegen. Eine andere bequeme Beschreibung ist die Verwendung der Ebenengleichung:
wobei A, B, C und D Koeffizienten sind, die durch die Koordinaten der Punkte T1, T2 und T3 berechnet werden,
(A, B, C) ist der Normalenvektor zur Ebene, D ist der freie Koeffizient. Das erhaltene Ergebnis kann normalisiert werden
(a, b, c) ist der Einheitsnormalenvektor zur Ebene, d ist der Abstand von der Ebene zum Koordinatenursprung (mit „plus“, wenn der Normalenvektor auf die den Ursprung enthaltende Halbebene gerichtet ist, sonst – mit „minus“).
Der Normalenvektor zur Ebene kann mit der statischen Funktion normal(…) berechnet werden, deren Argumente entweder drei Punkte (wie oben gezeigt) oder zwei nicht kollineare Vektoren sein können. Es ist offensichtlich, dass zwei Vektoren als T1 – T2, T2 – T3 und die Normale als ihre Vektorlinie angegeben werden können.
QVector3D v1(0,0,1), v2(0,1,0), v3(1,0,0); // корректно заданная плоскость qDebug() << QVector3D::normal(v1, v2, v3), QVector3D::normal(v1 - v2, v2 - v3), QVector3D::crossProduct(v1 - v2, v2 - v3).normalized();
> QVector3D(-0.57735, -0.57735, -0.57735) QVector3D(-0.57735, -0.57735, -0.57735) QVector3D(-0.57735, -0.57735, -0.57735)
Wenn die oben beschriebenen Eingabedatenanforderungen nicht erfüllt sind, ist das Ergebnis ein Nullvektor
QVector3D v01(0,0,10), v02(0,0,20), v03(0,0,30); // некорректно заданная плоскость qDebug() << QVector3D::normal(v01, v02, v03) << QVector3D::normal(v01 - v02, v02 - v03);
> QVector3D(0, 0, 0) QVector3D(0, 0, 0)
Die obige Beschreibung des Flugzeugs ist die kompakteste (4 Zahlen). Am bequemsten ist jedoch die Beschreibung der Ebene durch einen Punkt (T0) und einen Normalenvektor (6 Zahlen). Diese Ansicht erleichtert die Organisation der folgenden Vorgänge.
Zugehöriges Flugzeug
Wenn der Punkt T auf einer Ebene liegt und seine Koordinaten in die Gleichung der Ebene eingesetzt werden, erhalten wir 0. In der Darstellung durch einen Punkt und einen Vektor
Mit anderen Worten, der Vektor T - T0 steht senkrecht auf dem Normalenvektor n.
In einem Halbraum
Die Ebene teilt den Raum in zwei Halbräume. Der vorherige Ausdruck nimmt positive Werte an, wenn der Punkt T in einem von ihnen liegt, und negative Werte, wenn er in dem anderen liegt. Daher liegen 2 Punkte T1 und T2 im gleichen Halbraum if
Projektion des Punktes T auf eine Ebene
Entfernung von Punkt zu Ebene
ist der Abstand von einem Punkt zu seiner Projektion auf eine Ebene
Sie wird mit der Funktion distanceToPlane(...) definiert, deren Argumente die Ebenenparameter sind (ein Punkt und ein Vektor oder drei Punkte).
QVector3D T(2,2,2);// точка QVector3D n(-1,-1,-1), T0(1,1,1);// плоскость qDebug() << T.distanceToPlane(T0,n) << QVector3D::dotProduct(T-T0, n);// неправильный расчёт: нормальный вектор не нормализован qDebug() << T.distanceToPlane(T0,n.normalized()) << QVector3D::dotProduct(T-T0, n)/n.length();//правильный расчёт
>-3 -3
-1,73205 -1,73205
Beachten Sie, dass der resultierende Wert je nachdem, in welcher Halbebene T liegt, vorzeichenbehaftet ist.
Spiegelreflexion des T-Punktes von der Ebene
Gerade
Eine Linie im Raum wird vollständig durch zwei (nicht zusammenfallende) Punkte T1 und T2 beschrieben. In Qt ist es üblich, eine Gerade mit einem Punkt T1 und einem Richtungsvektor K = T2 – T1 zu beschreiben.
oder
wobei (ΔX, ΔY, ΔZ) = K. Es ist auch sinnvoll, diesen Vektor zu normieren
Zugehörigkeit des Punktes T zu einer Geraden
Die Vektoren (T – T1) und k müssen auf Kollinearität (Kreuzprodukt – Nullvektor) überprüft werden.
Projektion von Punkt T gerade
Der folgende Ausdruck kann leicht durch Analysieren der Lösungen des Problems der Projektion eines Punktes auf eine Ebene erhalten werden
Abstand vom Punkt T zur geraden Linie
In Übereinstimmung mit dem oben Gesagten
Der Abstand von einem Punkt zu einer Linie wird durch die Funktion distanceToLine(...) bestimmt, deren Argumente ein Punkt und ein Richtungsvektor der Linie sind.
QVector3D T(2,2,2);// точка QVector3D k(1,1,0), T1(0,0,1);// плоскость QVector3D R = T - T1 - k*QVector3D::dotProduct(k, T - T1); qDebug() << T.distanceToLine(T1,k) << R.length(); // неправильный расчёт: направляющий вектор не нормализован R = T - T1 - k*QVector3D::dotProduct(k, T - T1)/k.lengthSquared(); qDebug() << T.distanceToLine(T1,k.normalized()) << R.length(); // правильный расчёт
>3 3
1 1
Schnittpunkt einer Geraden und einer Ebene
Die Linie (k, T1) kann parallel zur Ebene (n, T0) sein. In diesem Fall stehen die definierenden Vektoren senkrecht, was durch Prüfung auf Gleichheit ihres Skalarprodukts überprüft wird. Andernfalls haben die Linie und die Ebene einen Schnittpunkt, der als definiert ist
In der obigen Formel ist es im Gegensatz zu den vorherigen nicht erforderlich, normalisierte Vektoren zu verwenden. Wenn es keinen Schnittpunkt gibt, kann überprüft werden, ob der Punkt T1 zu der Ebene gehört, und festgestellt werden, ob die Linie darauf liegt.
Fazit
Danke an alle, die den Artikel bis zum Ende gelesen haben. In Zukunft plane ich, dieses Thema zu entwickeln. Wenn Sie also Fragen zum Thema haben, schreiben Sie diese in die Kommentare.
У меня руки так и не дошли написать статью, где показан пример движения круга с отражениями от условных границ в графической сцене с использованием скалярных векторов.
Вообще прлюс использования скалярной математики при расчётах углов отражений в том, что это менее затратный операции, чем использование тригонометрии (синусы/косинусы и т.д.)
Скалярная математика и тригонометрия - суть одно и тоже. Просто разные языки описания одного предмета. В конечном итоге для расчёта коэффициента отражения нужно знать косинус угла падения.
Подскажите, какой инструмент лучше использовать для визуализации 3D объектов.
Вы очень сильно заблуждаетесь.
Вот формула для расчёта отражённого вектора. Расчёт углов через косинусы и синусы не требуется от слова совсем .
Подробнее обсуждение в этом топике
При написании арканоида для медицинского оборудования я пользовался именно математикой скалярных векторов. Поэтому и утверждаю, что использование скалярной математики в разы разгружает процессорные мощности. Это основы написания игровых движков.
Визуализацию можно и в Qt делать. Насколько помню по новостям там есть поддержка импорта 3D объектов даже. Также есть примеры визуализации некоторых вещей в самом Qt Creator.
На нашем проекте используется OpenCascade - достаточно мощная вещь. Главное написать код для QWidget, чтобы отображать результат. Но сам я в той части кода не работаю. Не моя область проекта.
Не согласен. Произведение l на n - это косинус угла между ними умноженный на их длины. Если бы оба вектора были единичными, то остался бы "чистый" косинус. Просто при используемом описании тригонометрия скрыта от нас.
А в плане того, что здесь не нужно вычислять угол, а потом по нему считать косинус я согласен. Непосредственного использования пригонометрических функций не требуется.
Да, согласен. Освежил в памяти. Вот определение из лекций
Тем не менее, косинус здесь знать не нужно, вот к чему я это говорил
Естественно, что скалярное произведение векторов отражает явления тригономитреческих функций и может быть сопоставлено. Иначе это было бы странно. Насколько помню - это выводится через последовательное доказательство леммами, но не через прямые расчёты и формулы. Поэтому утверждение имеет доказательную базу, а знать косинус не нужно ;-)