Дмитрий
Дмитрий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
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

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