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
GI

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

  • Result:60points,
  • Rating points-1
t

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

  • Result:70points,
  • Rating points1
LD

C++ - Тест 003. Условия и циклы

  • Result:57points,
  • Rating points-2
Last comments
VR

QML - Lesson 016. SQLite database and the working with it in QML Qt

Помогите, пожалуйста. У меня похожая задача, но я в qml слой долен передать не чистый запрос, а со сложной обработкой, поэтому у меня в С++ слое есть иерархия классов, которая имитирует бд и зап…
e
  • eviza
  • Nov. 16, 2020, 4:32 a.m.

Qt/C++ - Tutorial 083. Creating a dynamic library and connecting it to another project

здравствуйте! при компиляции библиотеки выскакивает окно особая программа( не удалось найти программу, укажите путь к ней), и в папке debug создается файл .dll, а .lib нет. подскажите…
IB

Data encryption by RSA algorithm in Qt with public and private keys without binding to OpenSSL

Библиотека подключилась нормально, только на выводе из первого примера выходит пустое сообщение, вместо "test message" просто "". Никаких ошибок не выдает.
VS

Qt WinAPI - Lesson 002. How to make win installer for Qt apllication?

Можно ли как-то однозначно (не проверяя) выяснить, запустится ли программа в windows 7? И как быть с разрядностью уже и просто в w10 (32\64)?
DT

Django - Tutorial 036. How to add authentication through social networks. VKontakte

Возможно, автор прочитает. Делал авторизацию с помощью ВК по книге Дронова. Выдает ошибку "Backend not found". Стал гуглить, нашел вашу статью, вроде почти то же самое (оно и понятно, документа…
Now discuss on the forum
m

Отправка JSON

Спасибо!

Разный масштаб в формах и при запуске

Сврестайте все в один лэйаут (Выбирите окно и нажмити сверху на голубой квадратик из 9 голубфх квадратиков)

Обращение к ячейке таблицы

Вам нужно наследоваться от QAbstractTableModel, выбрать в каких контейнерах и как будите хранить данные и уже у них по индексу будите получать данные. Вот под рукой пример был на питоне, на…
DK

QTableView не становится в редактирование

балин, вот я, конечно, "молодец". минус 5 часов, из-за того, что не поставил в модели флаги. Qt::ItemFlags UserModel::flags(const QModelIndex &index) const{ Qt::ItemFlags flags = Tre…

QSqlTableModel - Как добавить картинки в таблицу, чтобы они отражались в диалоговом окне, но не были частью модели

Ну тогда в этом столбце указывайте пути на несколько картинок
About
Services
© EVILEG 2015-2020
Recommend hosting TIMEWEB