DmitrijJune 28, 2020, 8:03 a.m.

Компьютерная геометрия с Qt Creator

Content

По средствам этой статьи хочу поделиться опытом по использованию компьютерной геометрии, который накопился у меня в процессе работы над диссертацией. Не все знают Qt Creator содержит инструменты для работы с геометрией (в частности QVector3D), подробного руководства к которым на русском языке нет. Поэтому здесь я постараюсь максимально кратко изложить необходимую теорию и её реализацию.

Точка и вектор

Для описания точки в Qt предусмотрены классы QVector2D, QVector3D и QVector4D (не путать с классом QVector) для двухмерного, трехмерного и четырёхмерного пространств соответственно. По сути это массивы из двух, трёх и четырёх переменных типа float с набором геометрических функций. Поэтому всё примеры буду приводить только для наиболее полезного на практике QVector3D.

QVector3D v1(1,1,1) v2(1,1,1), v3(1.1f,2.5f,3.8f);

В примере заданы 3 вектора. В скобрах записаны координаты х, у, z. Любую из компонент вектора можно получить или переопределить.

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)

Эти же классы применяются для описания векторов. Поэтому некоторые из функций будут иметь разные смыслы применительно к векторам и к точкам. Так, например, нормализация – это операция, которая преобразует заданный вектор в вектор единичной длины:

где – длина (модуль) вектора. В QVector3D для этого есть две функции:

v1.normalize(); // функция, изменяющая переменные объект
qDebug() << v1 << v2.normalized(); // функция, не переменные объект

QVector3D(0.57735, 0.57735, 0.57735) QVector3D(0.57735, 0.57735, 0.57735)

В примере вектор, имевший длину корень из трёх, был пересчитан в единичный вектор (такие нормализованные вектора в геометрии необходимы для различных геометрических операций). Это легко проверить с помощью функции length(), возвращающей длину вектора.

float L = v1.length(); // длина вектора или расстояние от точки до начала координат

В некоторых задачах нам интересна не длина вектора, а её квадрат. Для этого предусмотрена функция lengthSquared(), уменьшающая количество вычислений

float L2 = v1.lengthSquared();

Перед проведением многих вычислений необходимо проверить, не является ли используемые векторы нулевыми (все компоненты равны нулю). Иначе полученный результат окажется неадекватным (возможны даже ошибки).

bool b = v1.isNull(); // проверка: является ли вектор нулевым?

Вектора можно складывать и вычитать (при этом складываются их соответствующие координаты). Также вектор можно умножить или разделить на число (получает вектор в n раз большей или меньшей длины). Внимательней следует относиться к операции умножения, т.к. выражение v1*v2 приведём к перемножению соответствующих координат векторов. Аналогично работает операция деления v1/v2. Геометрическая трактовка двух предыдущих операций – растяжение и сжатие системы координат по соответствующим осям на величины заданные в v2. Для реализации скалярного и векторного произведений предусмотрены функции dotProduct(v1, v2) и crossProduct(v1, v2).

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)

Напомню, что скалярное произведение двух векторов – число, определяемое как

и в случае перемножения нормальных векторов равно косинусу угла между ними.
Векторное произведение можно вычислить как

где i, j, k – орты декартовой системы координат, прямые скобки (здесь и далее) – определитель матрицы. Результат данной операции – вектор, перпендикулярный к плоскости в которой лежат два вектора-сомножителя, или же нулевой вектор, если v1 и v2 коллинеарные. Модуль от векторного произведения равен произведению модулей сомножителей на синус угла между ними.
Векторы можно проверять на равенство. Два вектора равны тогда, когда равны все их соответствующие координаты. Однако следует помнить, что при сложных вычислениях может накапливаться погрешность. Поэтому разработчики предусмотрели возможность «мягкого» сравнения.

v2 = v1 + QVector3D(0.000001f, 0.000001f, 0.000001f);
qDebug() << (v1 == v2) << qFuzzyCompare(v1, v2);

false true

Расстояние между двумя точками определяется выражением

заложенном в функцию distanceToPoint(v2)

QVector3D v1(0,1,1), v2(1,0,1);
qDebug() << v1.distanceToPoint(v2);

1.41421

Для векторов смыслом последней операции является длина вектора их разности.

qDebug() << (v1-v2).length();

1.41421

Плоскость

Любая плоскость может быть задана тремя попарно несовпадающими своими точками T1, T2 и T3, не лежащими на одной прямой. Другой удобное описание – использование уравнения плоскости:

где A, B, С и D – коэффициенты, которые вычисляются через координаты точек T1, T2 и T3,

(A, B, C) – нормальный вектор к плоскости, D – свободный коэффициент. Полученный результат целесообразно нормировать

(a, b, c) – единичный нормальный вектор к плоскости, d – расстояние от плоскости до начала координат (с «плюсом», если нормальный вектор направлен в полуплоскость, содержащую начало координат, иначе – с «минусом»).
Нормальный вектор к плоскости можно вычислить с помощью статической функции normal(…), аргументами которой могут являться либо три точки (как показано выше), либо два неколлинеарных вектора. Очевидно, что два вектора могут быть заданы как T1 – T2, T2 – T3, а нормаль как их векторное проведение.

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)

Если описанные выше требования к исходным данным не выполнены, результатом будет нулевой вектор

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)

Приведённое описание плоскости является наиболее компактным (4 числа). Но наиболее удобным является описание плоскости одной точкой (T0) и нормальным вектором (6 чисел). Такое представление позволяет легко организовать следующие операции.

Принадлежность плоскость

Если точка T лежит на плоскости, то подставив в уравнение плоскости её координаты получим 0. В представлении через точку и вектор

Другими словами вектор T – T0 перпендикулярен нормальному вектору n.

В одном полупространстве

Плоскость делит пространство на два полупространства. Предыдущее выражение принимает положительные значения, если точка T лежит в одном из них и отрицательные, если в другом. Поэтому 2 точки T1 и T2 лежит в одном полупространстве если

Проекция точки T на плоскость

Расстояние от точки до плоскости

это расстояние от точки до её проекции на плоскость

Определяется оно с помощью функции distanceToPlane(…), аргументами которой являются параметры плоскости (точка и вектор или три точки).

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

Обратите внимание, что получаемое значение имеет знак в соответствии с там, в какой полуплоскости лежит T.

Зеркальное отражение точки T от плоскости

Прямая

Прямая в пространстве полностью описывается двумя (несовпадающими) точками T1 и T2. В Qt принято описывать прямую точкой T1 и направляющим вектором K = T2 – T1.

или

где (ΔX, ΔY, ΔZ) = K. Этот вектор также целесообразно нормировать

Принадлежность точки T прямой

Необходимо проверить вектора (T – T1) и k на коллинеарность (векторное произведение – нулевой вектор).

Проекция точки T прямую

Следующее выражение легко получить, проанализировав решения задачи о проекции точки на плоскость

Расстояние от точки T до прямой

В соответствии с изложенным выше

Расстояние от точки до прямой определяется функцией distanceToLine(…), аргументами которой являются точка и направляющий вектор прямой.

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

Пересечение прямой и плоскости

Прямая (k, T1) может быть параллельна плоскости (n, T0). В этом случае задающие векторы перпендикулярны, что проверяется проверкой на равенство их скалярного произведения. В противном случае прямая и плоскость имеют точку пересечения, определяемую как

В приведённой формуле, в отличие от предыдущих, не обязательно использовать нормализованные вектора. При отсутствии пересечения можно проверить принадлежность точки T1 плоскости, и установить лежит ли на ней прямая.

Заключение

Спасибо всем кто дочитал статью до конца. В будущем я планирую развить данную тему. Поэтому, если у вас есть вопросы по теме пишите их в комментариях.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

У меня руки так и не дошли написать статью, где показан пример движения круга с отражениями от условных границ в графической сцене с использованием скалярных векторов.

Вообще прлюс использования скалярной математики при расчётах углов отражений в том, что это менее затратный операции, чем использование тригонометрии (синусы/косинусы и т.д.)

Скалярная математика и тригонометрия - суть одно и тоже. Просто разные языки описания одного предмета. В конечном итоге для расчёта коэффициента отражения нужно знать косинус угла падения.

Подскажите, какой инструмент лучше использовать для визуализации 3D объектов.

Вы очень сильно заблуждаетесь.

Вот формула для расчёта отражённого вектора. Расчёт углов через косинусы и синусы не требуется от слова совсем .

Подробнее обсуждение в этом топике

При написании арканоида для медицинского оборудования я пользовался именно математикой скалярных векторов. Поэтому и утверждаю, что использование скалярной математики в разы разгружает процессорные мощности. Это основы написания игровых движков.

Визуализацию можно и в Qt делать. Насколько помню по новостям там есть поддержка импорта 3D объектов даже. Также есть примеры визуализации некоторых вещей в самом Qt Creator.

На нашем проекте используется OpenCascade - достаточно мощная вещь. Главное написать код для QWidget, чтобы отображать результат. Но сам я в той части кода не работаю. Не моя область проекта.

Не согласен. Произведение l на n - это косинус угла между ними умноженный на их длины. Если бы оба вектора были единичными, то остался бы "чистый" косинус. Просто при используемом описании тригонометрия скрыта от нас.

А в плане того, что здесь не нужно вычислять угол, а потом по нему считать косинус я согласен. Непосредственного использования пригонометрических функций не требуется.

Да, согласен. Освежил в памяти. Вот определение из лекций

Скалярным произведением ненулевых векторов a и b называется число, равное произведению длин этих
векторов на косинус угла между ними. Если среди векторов a и b
есть хотя бы один нулевой, то скалярное произведение равно нулю.

Тем не менее, косинус здесь знать не нужно, вот к чему я это говорил

В конечном итоге для расчёта коэффициента отражения нужно знать косинус угла падения.

Естественно, что скалярное произведение векторов отражает явления тригономитреческих функций и может быть сопоставлено. Иначе это было бы странно. Насколько помню - это выводится через последовательное доказательство леммами, но не через прямые расчёты и формулы. Поэтому утверждение имеет доказательную базу, а знать косинус не нужно ;-)

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Timeweb

Let me recommend you the excellent hosting on which EVILEG is located.

For many years, Timeweb has been proving his stability.

For projects on Django I recommend VDS hosting

View Hosting
k
  • knobu
  • Sept. 23, 2020, 2:34 a.m.

C++ - Test 006. Enumerations

  • Result:60points,
  • Rating points-1
k
  • knobu
  • Sept. 23, 2020, 2:21 a.m.

C++ - Test 005. Structures and Classes

  • Result:91points,
  • Rating points8
k
  • knobu
  • Sept. 23, 2020, 2:16 a.m.

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
Last comments

Qt/C++ - Lesson 006. QSqlQueryModel – Tables in Qt with SQL-query

QSqlTableModel выполняет ряд стандартных операций для одной таблицы из базы данных. Поэтому там и реализован функционал по удалению и редактированию. QSqlQueryModel позволяет выполнить запр…
VB

Qt/C++ - Lesson 006. QSqlQueryModel – Tables in Qt with SQL-query

Добрый день. Хотел спросить вот что. Создал проект на основе QAbstractTableModel. В MainWindow cоответственно создал модель и связал с представлением. Поиск веду по списку элементов модели,…

QCheckBox в качестве делегата QTableView

До тех пор, пока у вас проект содержит только одну таблицу, или несколько то может быть. Когда их будет 1000 и чекбоксы в разных колонках, то без делегатов и переопределения возвращаемых ре…
D
  • Damir
  • Sept. 20, 2020, 3:34 p.m.

QCheckBox в качестве делегата QTableView

bool Node::setData(const QModelIndex& index, const QVariant& value, int role){ switch (index.column()) { case 0: switch (role) { case Qt::CheckStateRole:// <- т…
VB

Qt/C++ - Lesson 004. QSqlTableModel – How to present the table from database?

Почему-то такой метод для обновления не работает, который можно было бы применить в данном примере. То есть в представлении данные удаляются и обновляются, а в базе данных изменений не происходи…
Now discuss on the forum

Как в qml работать с динамически созданными потомками?

В QML есть сборщик мусора, он может удалять объекты не сразу а по ппрошествии времени. Попробуйте при удалении вызывать сборщик мусора принудительно через gc()
VB

Как запустить программу с базой данных PostgreSQL на другом компьютере

Не требует никакую библиотеку, запускается на других компьютерах, где не установлена PostgreSQL, но создать элемент невозможно, тем более отредактировать или удалить.
p
  • prod1s
  • Sept. 24, 2020, 7:12 a.m.

через QT не могу открыть файл SQLite

Вирішення знайшов. Вказав замість назви БД об'єкт класу QSqlDataBase для QSqlQuery. QSqlQuery m_query = QSqlQuery(qSqlDataBase); Після двох днів пошуку рішення, все-таки знайшов…
U

как скрыть елемент с копии виджета

Дело в том, что ui класса находится в private-секции... И из-вне доступ получить, не нарушая канонов - не получится) Можно конечно сделать что-то в духе #define private public, но это для истинн…

MyForm(forms.Form): - непонятка

лично я бы сделал так: в get_context_data я бы генерил основной контекст, в а методах get и post я бы уже добавлял туда формы, правильно инициализированные. Т.е. class MySettingsCha…
About
Services
© EVILEG 2015-2020
Recommend hosting TIMEWEB