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

Tesztkörnyezet II

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

  • Poligonhálók reprezentációja
    • Fájlformátumok
    • Fél-él adatstruktúra
  • OpenMesh könyvtár
    • Osztály definiálása
    • Iterátorok, cirkulátorok
  • libQGLViewer/OpenGL
    • Kamera használata
    • Megjelenítési módok (wireframe / solid)

Második fázis

  • Mit tud?
    • Háromszöghálók beolvasása
      • STL, PLY, OBJ stb. fájlformátumok
    • Megjelenítés
      • W: wireframe ON/OFF
      • S: solid ON/OFF
  • Ehhez:
    • Qt [menü, fájlnyitás-dialógus]
    • OpenGL [megjelenítés]
    • OpenMesh
      • Hatékony háromszögháló implementáció
      • Standard fájlformátumok kezelése

A projekt felépítése

  • Közös Object ősosztály
  • BaseMesh háromszögháló a háttérben
    • Megjelenítés
    • Normális/görbület approximáció
  • Vizualizációs segédletek
  • Konkrét felülettípusok
    • Mesh: háromszögháló
    • Bezier: Bézier-felület

Poligonhálók reprezentációja

  • Hogyan érdemes tárolni...
    • ... fájlokban? [a méret a lényeg]
    • ... a memóriában? [a használhatóság a lényeg]
  • Triviális megoldás (háromszögekre):
    T1Ax, T1Ay, T1Az, T1Bx, T1By, T1Bz, T1Cx, T1Cy, T1Cz
    T2Ax, T2Ay, T2Az, T2Bx, T2By, T2Bz, T2Cx, T2Cy, T2Cz
    ...
    TnAx, TnAy, TnAz, TnBx, TnBy, TnBz, TnCx, TnCy, TnCz
    
  • Pl. STL formátum
  • Mi ezzel a probléma?
    • Nincs topológiai információ
    • Ugyanaz a csúcs többször szerepel

Ügyesebb reprezentáció

  • Minden vertexet egyszer tárol
  • Hivatkozás indexekkel
  • Például (PLY formátum):
    [...header...]
    P1x P1y P1z
    P2x P2y P2z
    ...
    Pmx Pmy Pmz
    3 T1A T1B T1C
    3 T2A T2B T2C
    ...
    3 TnA TnB TnC
    
  • Obj, VTK formátumok hasonló elven
  • Fájlban tárolásra jó, de kevés topológiai információ

Milyen a jó adatstruktúra?

  • Hatékony műveletek nagy háromszöghálókra
    • Lekérdezések
      • Topológiai (szomszédsági információk)
      • Geometriai (koordináták, szögek, élhosszak)
    • Átstrukturáló operációk (pl. éltörlés)
    • Egyéb műveletek
      • Normális- és görbületbecslés
      • Adott sugáron belüli pontok kigyűjtése
  • Univerzális
    • n-oldalú lapok
    • Külső és belső hurkok (lyukak)

Fél-él adatstruktúra

  • Irányított élpárok, orientált felületek
    • Az "anyag" a baloldalon
    • Csúcs:
      • Egy (tetszőleges) fél-él
      • Koordináták stb.
    • Fél-él:
      • Kezdőpont
      • Pár ("twin", mindig létezik)
      • Lap (ha van)
      • Előző és/vagy következő fél-él
    • Lap:
      • Egy fél-él minden hurokhoz

Ujjgyakorlat

Hol vannak az alábbi sokszöghálón a fél-élek? (satírozás → lyuk)

structure

Megoldás

Hol vannak az alábbi sokszöghálón a fél-élek? (satírozás → lyuk)

structure

Fejgyakorlat

Egy fél-él adatstruktúra alapján...

  1. Hogyan gyűjtjük össze egy csúcs körüli szomszédos csúcsokat?

  2. Hogyan gyűjtjük össze egy csúcs körüli szomszédos lapokat?

  3. Hogyan találjuk meg, és hogyan megyünk végig a mesh határán?
    (ha nincsenek lyukak)

Megoldás

  1. Hogyan gyűjtjük össze egy csúcs körüli szomszédos csúcsokat?
    → első csúcs: a félél párjának kezdőpontja
    → köv. félél: a pár rákövetkező félélje
    → köv. csúcs: az új félélből hasonlóan...
  2. Hogyan gyűjtjük össze egy csúcs körüli szomszédos lapokat?
    → Ugyanaz, kezdőpont helyett lap
  3. Hogyan találjuk meg, és hogyan megyünk végig a mesh határán?
    (ha nincsenek lyukak)
    → első félél: ahol nincs lap
    → köv. félél: a rákövetkező (!)

OpenMesh

  • Honlap: http://www.openmesh.org/
  • RWTH Aachen egyetem (Leif Kobbelt)
  • Általános és hatékony reprezentáció
    • Poligonhálók
    • Fél-él struktúra
  • Alapvető algoritmusok
    • Pl. normálbecslés, decimálás, simítás
  • Fontosabb fájlformátumok támogatása
    • STL, PLY, OBJ, IGES stb.
  • Lightweight (vö. CGAL, OpenCascade)
  • Dokumentáció: szűkszavú, de van sok példa

Az OpenMesh használata

  • Mesh = TriMesh/PolyMesh + Kernel + Traits
  • Kernel: belső tárolás (pl. ArrayKernel)
  • Traits: testreszabás
    • Koordináták, pontok típusai
    • Plusz információk hozzárendelése
  • base-mesh.hh:
    #include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
    struct BaseTraits : public OpenMesh::DefaultTraits {
      using Point  = OpenMesh::Vec3d; // the default would be Vec3f
      using Normal = OpenMesh::Vec3d;
      VertexTraits {
        double mean;                  // approximated mean curvature
      };
    };
    using BaseMesh = OpenMesh::TriMesh_ArrayKernelT<BaseTraits>;
    using Vector = OpenMesh::VectorT<double,3>;
    

Egyéb hasznos funkciók

  • Bejárás
    • Iterátorok (csúcsok, (fél)élek, lapok)
    • Cirkulátorok (pl. VertexFaceIter típus)
    • range objectek is (pl. faces(), vertices() stb.)
  • Fájlbeolvasás
    • OpenMesh::IO::read_mesh(mesh, filename)
    • Fájlformátumot automatikusan felismeri
  • Normálisok hozzárendelése (lap/csúcs)
    • request_face_normals / request_vertex_normals
      • Csak lefoglalja a helyet
    • update_face_normals / update_vertex_normals
      • Ez végzi el a tényleges számolást

Beolvasás

  • Open és Import action a File menübe
    • Open: törli az eddig betöltött objektumokat
    • Import: nem töröl, csak beolvas
  • A beolvasást majd a Viewer végzi
  • Megjegyzi, hogy melyik könyvtárban volt legutóbb
class Window : public QMainWindow {
  // ...
private slots:
  void open(bool clear_others);
  // ...
private:
  QString last_directory;
  // ...
};

window.cc

Window::Window(QApplication *parent) :
  QMainWindow(), parent(parent), last_directory(".")
{
  // ...
  auto openAction = new QAction(tr("&Open"), this);
  openAction->setShortcut(tr("Ctrl+O"));
  openAction->setStatusTip(tr("Load a model from a file"));
  connect(openAction, &QAction::triggered, [this](){ open(true); });

  auto importAction = new QAction(tr("&Import"), this);
  importAction->setShortcut(tr("Ctrl+I"));
  importAction->setStatusTip(tr("Import a model from a file"));
  connect(importAction, &QAction::triggered, this, [this](){ open(false); });

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

window.cc (folytatás)

void Window::open(bool clear_others) {
  auto filename =
    QFileDialog::getOpenFileName(this, tr("Open File"), last_directory,
                                 tr("Readable files (*.obj *.ply *.stl);;"
                                    "Mesh (*.obj *.ply *.stl);;"
                                    "All files (*.*)"));
  if (filename.isEmpty())
    return;
  last_directory = QFileInfo(filename).absolutePath();

  if (clear_others)
    viewer->deleteObjects();

  if (!viewer->open(filename.toUtf8().data()))
    QMessageBox::warning(this, tr("Cannot open file"),
                         tr("Could not open file: ") + filename + ".");
}

Viewer változások

  • Objektumok megnyitása, tárolása, törlése
  • Vizualizációs opciók, ezek közt váltás billentyűkkel
  • Kamera beállítások
#include "object.hh"

class Viewer : public QGLViewer {
  Q_OBJECT

public:
  void deleteObjects();
  bool open(std::string filename);
  // ...

private:
  void setupCamera();
  std::vector<std::shared_ptr<Object>> objects;
  Visualization vis;
};

visualization.hh

#pragma once

#include <GL/gl.h>

struct Visualization {
  Visualization();
  bool show_solid, show_wireframe;
};

visualization.cc

#include "visualization.hh"

Visualization::Visualization() :
  show_solid(true), show_wireframe(false)
{
}

viewer.cc

void Viewer::keyPressEvent(QKeyEvent *e) {
  if (e->modifiers() == Qt::NoModifier)
    switch (e->key()) {
    case Qt::Key_S:
      vis.show_solid = !vis.show_solid;
      update();
      break;
    case Qt::Key_W:
      vis.show_wireframe = !vis.show_wireframe;
      update();
      break;
    default:
      QGLViewer::keyPressEvent(e);
    } else
    QGLViewer::keyPressEvent(e);
}

object.hh

#pragma once

#include "base-mesh.hh"
#include "visualization.hh"

class Object {
public:
  explicit Object(std::string filename);
  virtual ~Object();
  const BaseMesh &baseMesh() const;
  virtual void draw(const Visualization &vis) const;
  virtual void updateBaseMesh();
  virtual bool reload() = 0;
  bool valid() const;
protected:
  BaseMesh mesh;
  std::string filename;
};

object.cc

#include "object.hh"

Object::Object(std::string filename) : filename(filename) { }
Object::~Object() { }
const BaseMesh &Object::baseMesh() const { return mesh; }
bool Object::valid() const { return mesh.n_vertices() > 0; }

void Object::draw(const Visualization &vis) const { // simple version
  for (auto f : mesh.faces()) {
    glBegin(GL_POLYGON);
    for (auto v : f.vertices()) {
      glNormal3dv(mesh.normal(v).data());
      glVertex3dv(mesh.point(v).data());
    }
    glEnd();
  }
}

void Object::updateBaseMesh() {
  mesh.request_face_normals(); mesh.request_vertex_normals();
  mesh.update_face_normals(); mesh.update_vertex_normals();
}

Mesh objektumok

  • A lehető legegyszerűbb objektum típus
  • Semmi pluszt nem tud a beolvasáson kívül (azt is az OpenMesh végzi)

mesh.hh

#pragma once

#include "object.hh"

class Mesh : public Object {
public:
  Mesh(std::string filename);
  virtual ~Mesh();
  virtual bool reload() override;
};

mesh.cc

#include <OpenMesh/Core/IO/MeshIO.hh>
#include "mesh.hh"

Mesh::Mesh(std::string filename) : Object(filename) {
  reload();
}

Mesh::~Mesh() { }

bool Mesh::reload() {
  if (!OpenMesh::IO::read_mesh(mesh, filename))
    return false;
  updateBaseMesh();
  return true;
}

Objektumok megnyitása, kirajzolása és törlése

#include "mesh.hh"

void Viewer::draw() {
  for (auto o : objects)
    o->draw(vis);
}

void Viewer::deleteObjects() {
  objects.clear();
}

bool Viewer::open(std::string filename) {
  std::shared_ptr<Object> surface;
  surface = std::make_shared<Mesh>(filename);
  if (!surface->valid())
    return false;
  objects.push_back(surface);
  setupCamera();
  return true;
}

Kamera beállítása

  • Kiszámítjuk a befoglaló dobozt
  • A többit elvégzi a libQGLViewer könyvtár
void Viewer::setupCamera() {
  double large = std::numeric_limits<double>::max();
  Vector box_min(large, large, large), box_max(-large, -large, -large);
  for (auto o : objects) {
    const auto &mesh = o->baseMesh();
    for (auto v : mesh.vertices()) {
      box_min.minimize(mesh.point(v));
      box_max.maximize(mesh.point(v));
    }
  }
  using qglviewer::Vec;
  camera()->setSceneBoundingBox(Vec(box_min.data()), Vec(box_max.data()));
  camera()->showEntireScene();
  update();
}

Wireframe vs. Solid

  • glPolygonMode
    • Csak wireframe → GL_FRONT_AND_BACK, GL_LINE
    • Különben → GL_FRONT_AND_BACK, GL_FILL
  • Solid + wireframe problémás
    • Kétszer kell kirajzolni
      • Egyszer a kitöltött, fehér, árnyalt háromszögeket
      • Egyszer csak a fekete körvonalakat
    • A másodiknál ki kell kapcsolni a világítást
    • Meg kell akadályozni, hogy egymásba essenek
      • glEnable(GL_POLYGON_OFFSET_FILL)
      • glPolygonOffset(1, 1)

A teljes draw() függvény

void Object::draw(const Visualization &vis) const {
  glPolygonMode(GL_FRONT_AND_BACK, !vis.show_solid && vis.show_wireframe ? GL_LINE : GL_FILL);
  glEnable(GL_POLYGON_OFFSET_FILL);
  glPolygonOffset(1, 1);
  glColor3d(1.0, 1.0, 1.0);
  if (vis.show_solid || vis.show_wireframe) {
    for (auto f : mesh.faces()) {
      glBegin(GL_POLYGON);
      for (auto v : f.vertices()) {
        glNormal3dv(mesh.normal(v).data());
        glVertex3dv(mesh.point(v).data());
      }
      glEnd();
    }
  }
  if (vis.show_solid && vis.show_wireframe) {
    glPolygonMode(GL_FRONT, GL_LINE);
    glColor3d(0.0, 0.0, 0.0);
    glDisable(GL_LIGHTING);
    for (auto f : mesh.faces()) {
      glBegin(GL_POLYGON);
      for (auto v : f.vertices())
        glVertex3dv(mesh.point(v).data());
      glEnd();
    }
    glEnable(GL_LIGHTING);
  }
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

Kész a második fázis

Phase 2 complete

![bg 50%](halfedge-legend.png)