3D-s számítógépes geometria és alakzatrekonstrukció

Tesztkörnyezet I

http://cg.iit.bme.hu/portal/node/312
https://portal.vik.bme.hu/kepzes/targyak/VIIIMA25

Dr. Várady Tamás, Dr. Salvi Péter
BME, Villamosmérnöki és Informatikai Kar
Irányítástechnika és Informatika Tanszék

Tartalom

  • Bevezetés
  • Qt alapismeretek
    • Minimális program felépítése
    • QMainWindow (menük, status bar stb.)
    • QProgressBar
  • libQGLViewer/OpenGL
    • Alapvető OpenGL rajzolás
    • OpenGL ablak beillesztése

Áttekintés

  • Cél: tesztkörnyezet felépítése
  • Segítség az önálló feladatokhoz
  • Sokrétű ismeret szükséges:
    • C++ ✓
    • OpenGL
    • Qt / libQGLViewer
    • OpenMesh
  • Kevés elmélet, sok “gyakorlat”
  • Házifeladat

A tesztkörnyezet

  • Alapvető felhasználói eszközök
    • Ablak- és menühasználat, progress bar stb.
    • 3D tér forgatása, kameraállapot/kép mentése, háromszöghálós és kitöltött megjelenítés
    • Pontok mozgatása a térben
  • Alapvető programozói eszközök
    • Pontok, vektorok, háromszöghálók kezelése
    • Pontok, szakaszok és sokszögek megjelenítése
    • Színezés, textúrázás
    • Néhány algoritmus (pl. görbületszámítás, simítás, Bézier felület kiértékelés)

Demó

  • Forráskód: GitHub
  • Órai feldolgozás több fázisban
  • Platform-független
    • Tesztelve: Linux, Windows, Mac
  • Linux virtuális gép (ha muszáj)
  • Probléma esetén Teams üzenet vagy email: salvi@iit.bme.hu
  • Bugok, javítási javaslatok ugyanide

Első fázis

  • Mit tud?
    • Menük, status bar
    • Négyzet kirajzolása
    • Forgatás, nagyítás, mozgatás
    • Help, teljes képernyő, kamera/kép mentése
    • p megnyomására progress bar teszt
  • Ehhez:
    • Qt
    • QGLViewer
    • OpenGL

Qt bevezető

  • Honlap: http://www.qt.io/
    • Trolltech → Nokia → Digia → The Qt Company
  • Nem kereskedelmi célra ingyenes
  • Platform-független GUI
  • Szuper dokumentáció (http://doc.qt.io/)
  • Saját IDE: Qt Creator
    • De összekapcsolható a Visual Studióval is
    • Tud Makefile-t generálni [qmake]
  • Online help, tutorialok, demók
  • GUI szerkesztő

Qt és C++

  • Saját "STL" könyvtár
    • std::stringQString
    • Saját konténerek (QList, QVector, QMap stb.)
    • Smart pointerek (weak/strong)
  • QObject ősosztály
    • Signal-Slot kommunikáció [ld. később]
    • Automatikus fába rendeződés (parent-child)
      • A szülők szabadítják fel a gyerekeiket
      • tr("...") a szövegek fordításához
      • Ehhez: Q_OBJECT makró (előfeldolgozás: moc)
  • Text projekt fájl (.pro) → helyette CMake-et használunk

CMake

  • Általános, cross-platform make rendszer
  • Programozható
  • Könnyen megtalálja a könyvtárakat
  • Készít Makefile-t vagy VS projekt fájlt
  • Jól együtt játszik a Qt-vel
  • Benne:
    • Beállítások (pl. Release/Debug)
    • Forrásfájlok nevei
    • Könyvtárak neve
    • Resource-ok

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)
project(geo-framework)

find_package(OpenMesh REQUIRED)
find_package(QGLViewer REQUIRED)
...

set(geo_HEADERS # headers processed by MOC
  viewer.hh window.hh )
qt6_wrap_cpp(geo_HEADERS_MOC ${geo_HEADERS})

include_directories(
  ${OPENMESH_INCLUDE_DIR} ${Qt6Widgets_INCLUDE_DIRS} ... )

add_executable(geo-framework WIN32
  ${geo_HEADERS_MOC} textures.qrc bezier.cc main.cc ... )

target_link_libraries(geo-framework PUBLIC
  ${OPENGL_LIBRARIES} ${OPENMESH_LIBRARIES} Qt6::Core ... )

Qt elnevezési séma

  • Osztályok:
    • QSomeClass (→ #include <QSomeClass> )
  • Konstansok:
    • QSomeClass::EnumTypeValueOne, ValueTwo;
    • Qt::EnumTypeQt::ValueOne, Qt::ValueTwo;
  • Metódusok:
    • QSomeClass::someMethod(...)
  • Tulajdonságok:
    • QSomeClass::someProperty()
    • QSomeClass::setSomeProperty(...)

Signal-Slot kommunikáció (1)

  • Események (event):
    • Minden GUI mozgató rugója
    • Felhasználói események
      (egér mozgatása, klikkelés, gépelés, gomb megnyomása stb.)
    • Független események
      (időzítés, egy widget állapotának megváltozása stb.)
  • Cél:
    • Reagálás az eseményre
    • Reakció lehet “független” widgetben
  • Hagyományos megoldás:
    • Callback függvény (paraméterátadás problémás, nem type-safe)

Signal-Slot kommunikáció (2)

  • Signal:
    • Valamilyen "eseményt" jelez
    • Osztály deklarálásánál signals:
    • Jelzés az emit paranccsal
    • Paraméterezhető
  • Slot:
    • Osztály deklarálásánál slots:
    • Összekapcsoláshoz connect makró
    • Paramétertípusoknak egyezni kell
    • Mindkét oldalon lehet többszörös
      (egy signalhoz több slot, egy slothoz több signal)

Programozás Qt-vel

  • Fő ablak osztály
    • Egy Qt osztályból származtatva
    • Q_OBJECT
    • Kibővítve (új signalok, slotok)
    • Testreszabva (virtuális metódusok újraírása)
  • Főprogram
    • QApplication meghívása
    • Ez felelős a GUI vezérléséért
  • Projekt fájl / [C]Makefile készítés
  • Fordítás

A főprogram

main.cc

#include <QtWidgets/QApplication>

#include "window.hh"

int main(int argc, char **argv) {
  QApplication app(argc, argv);
  Window window;
  window.show();
  return app.exec();
}

A fő ablak

  • QMainWindow-ból származtatjuk
    • Menüsor, toolbar, fő widget, status bar
  • File menü hozzáadása

window.hh

#pragma once

#include <QtWidgets/QMainWindow>

class Window : public QMainWindow {
  Q_OBJECT
public:
  Window();
};

window.cc

#include <QtWidgets>
#include "window.hh"

Window::Window() {
  setWindowTitle(tr("Geometry Framework"));
  setStatusBar(new QStatusBar);

  auto quitAction = new QAction(tr("&Quit"), this);
  quitAction->setShortcut(tr("Ctrl+Q"));
  quitAction->setStatusTip(tr("Quit the program"));
  connect(quitAction, &QAction::triggered, this, &Window::close);

  auto fileMenu = menuBar()->addMenu(tr("&File"));
  fileMenu->addAction(quitAction);
}

Progress bar hozzáadása

  • A program eddig:
    Phase 1
  • Progress Bar kezelés:
    • Csak a folyamat alatt jelenik meg
    • Három slot: start, mid, end
    • Nem kell mindegyiket használni
      (pl. gomb megnyomása / számolás vége)
    • Időt kell szakítani a grafika frissítésére
      (QApplication::processEvents(...))

window.hh

// ...
class QApplication;
class QProgressBar;

class Window : public QMainWindow {
  Q_OBJECT
public:
  explicit Window(QApplication *parent);
private slots:
  void startComputation(QString message);
  void midComputation(int percent);
  void endComputation();
private:
  QApplication *parent;
  QProgressBar *progress;
};

main.cc

  // ...
  QApplication app(argc, argv);
  Window window(&app);
  // ...

window.cc

Window::Window(QApplication *parent) : QMainWindow(), parent(parent) {
  // ...
  progress = new QProgressBar;
  progress->setMinimum(0); progress->setMaximum(100);
  progress->hide();
  statusBar()->addPermanentWidget(progress);
}

window.cc (folytatás)

void Window::startComputation(QString message) {
  statusBar()->showMessage(message);
  progress->setValue(0);
  progress->show();
  parent->processEvents(QEventLoop::ExcludeUserInputEvents);
}
void Window::midComputation(int percent) {
  progress->setValue(percent);
  parent->processEvents(QEventLoop::ExcludeUserInputEvents);
}
void Window::endComputation() {
  progress->hide();
  statusBar()->clearMessage();
}

libQGLViewer

  • Honlap: http://www.libqglviewer.com/
    vagy (inkább) GitHub
  • Kiegészítő könyvtár Qt-hez
    • OpenGL ablakkezelés Qt környezetben
    • Vektor osztály (egyben pont osztály is)
    • Kamera osztály (mátrixtranszformációk)
  • Sok hasznos funkció
    • Kameramozgatás
    • 3D kiválasztás
    • Teljes képernyőre váltás
    • Stb. (ld. help / dokumentáció)

viewer.hh

#pragma once

#include <QGLViewer/qglviewer.h>

class Viewer : public QGLViewer {
  Q_OBJECT

public:
  explicit Viewer(QWidget *parent);

signals:
  void startComputation(QString message);
  void midComputation(int percent);
  void endComputation();

protected:
  virtual void init() override;
  virtual void draw() override;
  virtual void keyPressEvent(QKeyEvent *e) override;
  virtual QString helpString() const override;
};

Virtuális függvények

  • void init():
    OpenGL beállítások inicializáláskor, pl.:
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
    [mindkét oldal legyen megvilágítva]
  • void draw():
    OpenGL kirajzolás
  • void keyPressEvent(QKeyEvent *):
    Eseménykezelő billentyű leütésekor
  • QString helpString() const:
    A helpben megjelenítendő szöveg

OpenGL rajzolás (old-school)

  • Nézet (camera) kezelése → libQGLViewer
  • Alapból a síkra néz
  • Általános struktúra:
    glBegin(OBJEKTUM_TÍPUS);
    glVertex3f(float, float, float);
    // ...
    glEnd();
    
  • GL_POINTS, GL_LINES, GL_POLYGON stb.
  • ...[234][sifd]v?
    • 2/3/4: dimenzió (4: homogén koordináták)
    • s/i/f/d: short, int, float, double
    • v: vektor, pl. 2fvfloat[2]-t adunk át

viewer.cc

#include <thread>
#include <QtGui/QKeyEvent>
#include "viewer.hh"

Viewer::Viewer(QWidget *parent) : QGLViewer(parent) { }

void Viewer::init() {
  glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
}

void Viewer::draw() {
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
  glBegin(GL_POLYGON);
  glVertex3f(-0.5, -0.5, 0.0); glVertex3f( 0.5, -0.5, 0.0);
  glVertex3f( 0.5,  0.5, 0.0); glVertex3f(-0.5,  0.5, 0.0);
  glEnd();
}

QString Viewer::helpString() const {
  return "<h2>Geometry Framework</h2> ...";
}

viewer.cc (folytatás)

void Viewer::keyPressEvent(QKeyEvent *e) {
  using namespace std::literals::chrono_literals;
  if (e->modifiers() == Qt::NoModifier)
    switch (e->key()) {
    case Qt::Key_P:
      emit startComputation(tr("Testing progress bar..."));
      for (size_t i = 1; i <= 10; ++i) {
        std::this_thread::sleep_for(0.3s);
        emit midComputation(i * 10);
      }
      emit endComputation();
      break;
    default:
      QGLViewer::keyPressEvent(e);
    } else
    QGLViewer::keyPressEvent(e);
}

window.hh

#include "viewer.hh"

class Window : public QMainWindow {
private:
  Viewer *viewer;
  // ...
};

window.cc

Window::Window(QApplication *parent) : QMainWindow(), parent(parent) {
  viewer = new Viewer(this);
  connect(viewer, &Viewer::startComputation, this, &Window::startComputation);
  connect(viewer, &Viewer::midComputation, this, &Window::midComputation);
  connect(viewer, &Viewer::endComputation, this, &Window::endComputation);
  setCentralWidget(viewer);
  // ...
}

Kész az első fázis

Phase 1 complete