Дмитрий
Дмитрий28 червня 2020 р. 08:03

Комп'ютерна геометрія з Qt Creator

Засобами цієї статті хочу поділитися досвідом використання комп'ютерної геометрії, який накопичився у мене в процесі роботи над дисертацією. Не всі знають 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);

> хибно правда

Відстань між двома точками визначається виразом

закладеному у функцію 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,577)

Якщо описані вище вимоги до вихідних даних не виконані, результатом буде нульовий вектор

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 площині, і встановити, чи лежить на ній пряма.

Висновок

Дякуємо всім, хто дочитав статтю до кінця. У майбутньому я планую розвинути цю тему. Тому, якщо у вас є питання на тему пишіть їх у коментарях.

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Evgenii Legotckoi
  • 29 червня 2020 р. 04:05

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

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

Дмитрий
  • 01 липня 2020 р. 05:56

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

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

Evgenii Legotckoi
  • 01 липня 2020 р. 06:09
  • (відредаговано)

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

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

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

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

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

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

Дмитрий
  • 02 липня 2020 р. 06:16

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

Дмитрий
  • 02 липня 2020 р. 06:19

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

Evgenii Legotckoi
  • 02 липня 2020 р. 06:50

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

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

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

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

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
e
  • ehot
  • 01 квітня 2024 р. 00:29

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

  • Результат:78бали,
  • Рейтинг балів2
B
  • Bogdannn
  • 28 березня 2024 р. 05:21

C++ - Тест 002. Константы

  • Результат:16бали,
  • Рейтинг балів-10
B
  • Bogdannn
  • 28 березня 2024 р. 05:15

C++ - Тест 001. Первая программа и типы данных

  • Результат:46бали,
  • Рейтинг балів-6
Останні коментарі
k
kmssr09 лютого 2024 р. 05:43
Qt Linux - Урок 001. Автозапуск програми Qt під Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко05 лютого 2024 р. 12:50
Qt WinAPI - Урок 007. Робота з ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 грудня 2023 р. 21:30
Boost - статичне зв&#39;язування в проекті CMake під Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 грудня 2023 р. 19:38
Boost - статичне зв&#39;язування в проекті CMake під Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 грудня 2023 р. 08:01
Qt/C++ - Урок 056. Підключення бібліотеки Boost в Qt для компіляторів MinGW і MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Тепер обговоріть на форумі
a
a_vlasov14 квітня 2024 р. 16:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 квітня 2024 р. 12:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex04 квітня 2024 р. 14:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…
P
Pisych27 лютого 2023 р. 15:04
Как получить в массив значения из связанной модели? Спасибо, разобрался:))
AC
Alexandru Codreanu19 січня 2024 р. 22:57
QML Обнулить значения SpinBox Доброго времени суток, не могу разобраться с обнулением значение SpinBox находящего в делегате. import QtQuickimport QtQuick.ControlsWindow { width: 640 height: 480 visible: tr…

Слідкуйте за нами в соціальних мережах