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

Tesztkörnyezet IV

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

  • Fairing [OpenMesh]
  • Pontmozgatás
    • Név szerinti szelekció [OpenGL]
    • Szelekciós segédfüggvények [libQGLViewer]
    • Kitérő egyenesek metszése

Negyedik fázis

  • Fairing
    • Laplace-féle simítás
    • Normális irányban mozgat
    • OpenMesh-be beépített
  • Pontmozgatás
    • XYZ tengelyek megjelenítése
    • libQGLViewer/OpenGL: kiválasztás támogatása
    • Problémás:
      • Takarás kezelése (választható pontok / tengelyek)
      • Tengelyméret (elvileg nem függ modelltől/nézettől)
    • Itt: egyszerű, de nem tökéletes megoldás

Fairing

  • Beépített függvény segítségével:
    #include <OpenMesh/Tools/Smoother/JacobiLaplaceSmootherT.hh>
    
    void Mesh::fair() {
      OpenMesh::Smoother::JacobiLaplaceSmootherT<BaseMesh> smoother(mesh);
      smoother.initialize(OpenMesh::Smoother::SmootherT<BaseMesh>::Normal,
                          OpenMesh::Smoother::SmootherT<BaseMesh>::C1);
      smoother.smooth(10);
    }
    
  • initialize(mozgatás_iránya, megtartandó_folytonosság)
  • Irány lehet: Tangential, Normal, Tangential_and_Normal
  • Folytonosság lehet: C0, C1, C2
  • smooth(iterációk_száma)

GUI kontroll

void Viewer::keyPressEvent(QKeyEvent *e) {
  if (e->modifiers() == Qt::NoModifier)
    switch (e->key()) {
    case Qt::Key_F:
      {
        std::vector<Mesh *> meshes;
        for (auto o : objects)
          try {
            meshes.push_back(dynamic_cast<Mesh *>(o.get()));
          } catch(std::bad_cast &) { }
        emit startComputation(tr("Fairing meshes..."));
        for (size_t i = 1; i <= 10; ++i) {
          for (auto m : meshes)
            m->fair();
          emit midComputation(i * 10);
        }
        for (auto m : meshes)
          m->updateBaseMesh();
        emit endComputation();
      }
      update();
      break;
    // ...
    default:
      QGLViewer::keyPressEvent(e);
    } else
    QGLViewer::keyPressEvent(e);
}

OpenGL szelekció - két módszer

  • Sugármetszés
    • Szemből kurzoron át menő vektor
    • Metszés az objektummal (manuális)
    • Előny: koordinátákat ad
  • Név szerinti szelekció
    • Újból renderelés
      • Elég csak a kurzor körül
      • Elég csak a választható dolgokat
      • De kellenek a takaró objektumok is
    • Keret:
      glPushName(int)
      // ...rajzolás...
      glPopName()
      

libQGLViewer támogatás

  • Sugármetszés
    • Camera::convertClickToLine(pont, szem, irány)
      • Visszaadja adott ponthoz a szem/irány vektorokat
    • Camera::pointUnderPixel(pont, talált-e)
      • Kurzorpozíció alatti objektumpontot adja vissza
      • Nagy színtérre pontatlan (valójában nem metszés)
    • Szem- és világkoordinátarendszer konverziók
  • Név szerinti szelekció
    • Shift+click : szelekció
    • drawWithNames(), postSelection()
    • Néhány hasznos property:
      • selectRegionWidth / selectRegionHeight, selectedName

Szerkezet

  • Ha a tengelyek nem láthatóak
    • Minden csúcs kiválasztható
    • Szelekciókor...
      • Idehelyezzük és megjelenítjük a tengelyeket
      • Elmentjük az aktuális pozíciót
  • Ha a tengelyek láthatóak
    • Csak a tengelyek választhatóak ki
    • Szelekciókor...
      • Megjegyezzük, melyik tengely
      • Elmentjük a klikkelt helyet
    • Egérmozgatáskor (szelekció után)
      • Tengelyek (és a csúcs) mozognak

Modification

class Viewer : public QGLViewer {
  // ...
protected:
  virtual void drawWithNames() override;
  virtual void postSelection(const QPoint &p) override;
  virtual void mouseMoveEvent(QMouseEvent *e) override;
  // ...
private:
  int selected_vertex;
  size_t selected_object;
  struct ModificationAxes {
    bool shown;
    float size;
    int selected_axis;
    Vector position, grabbed_pos, original_pos;
  } axes;
  // ...
};

Rajzolás tengelyekkel

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

  if (axes.shown) {
    using qglviewer::Vec;
    const auto &p = Vec(axes.position);
    glColor3d(1.0, 0.0, 0.0);
    drawArrow(p, p + Vec(axes.size, 0.0, 0.0), axes.size / 50.0);
    glColor3d(0.0, 1.0, 0.0);
    drawArrow(p, p + Vec(0.0, axes.size, 0.0), axes.size / 50.0);
    glColor3d(0.0, 0.0, 1.0);
    drawArrow(p, p + Vec(0.0, 0.0, axes.size), axes.size / 50.0);
  }
}

Rajzolás nevekkel

void Viewer::drawWithNames() {
  if (axes.shown) {
    using qglviewer::Vec;
    const auto &p = Vec(axes.position);
    glPushName(0);
    drawArrow(p, p + Vec(axes.size, 0.0, 0.0), axes.size / 50.0);
    glPopName();
    glPushName(1);
    drawArrow(p, p + Vec(0.0, axes.size, 0.0), axes.size / 50.0);
    glPopName();
    glPushName(2);
    drawArrow(p, p + Vec(0.0, 0.0, axes.size), axes.size / 50.0);
    glPopName();
  } else {
    for (size_t i = 0; i < objects.size(); ++i) {
      glPushName(i);
      objects[i]->drawWithNames(vis);
      glPopName();
    }
  }
}

Objektumok szelekciós függvényei

class Object {
public:
  virtual void drawWithNames(const Visualization &vis) const = 0;
  virtual Vector postSelection(int selected) = 0;
  virtual void movement(int selected, const Vector &pos) = 0;
  // ...
};

class Mesh : public Object {
public:
  virtual void drawWithNames(const Visualization &vis) const override;
  virtual Vector postSelection(int selected) override;
  virtual void movement(int selected, const Vector &pos) override;
  // ...
};

Mesh nevesített rajzolása & csúcsok mozgatása

void Mesh::drawWithNames(const Visualization &vis) const {
  if (!vis.show_wireframe)
    return;
  for (auto v : mesh.vertices()) {
    glPushName(v.idx());
    glRasterPos3dv(mesh.point(v).data());
    glPopName();
  }
}

Vector Mesh::postSelection(int selected) {
  return mesh.point(BaseMesh::VertexHandle(selected));
}

void Mesh::movement(int selected, const Vector &pos) {
  mesh.set_point(BaseMesh::VertexHandle(selected), pos);
}

Szelekció

void Viewer::postSelection(const QPoint &p) {
  int sel = selectedName();
  if (sel == -1) {
    axes.shown = false;
    return;
  }
  if (axes.shown) {
    axes.selected_axis = sel;
    bool found;
    axes.grabbed_pos = toVector(camera()->pointUnderPixel(p, found));
    axes.original_pos = axes.position;
    if (!found)
      axes.shown = false;
    return;
  }
  // ↓

Szelekció (folytatás) - tengelyek mérete

  // ↑
  using qglviewer::Vec;
  selected_vertex = sel;
  axes.position = objects[selected_object]->postSelection(sel);
  double depth = camera()->projectedCoordinatesOf(Vec(axes.position))[2];
  Vec q1 = camera()->unprojectedCoordinatesOf(Vec(0.0, 0.0, depth));
  Vec q2 = camera()->unprojectedCoordinatesOf(Vec(width(), height(), depth));
  axes.size = (q1 - q2).norm() / 10.0;
  axes.shown = true;
  axes.selected_axis = -1;
}

static inline Vector toVector(const qglviewer::Vec &v) {
  return Vector(static_cast<const qreal *>(v));
}

Modification

void Viewer::mouseMoveEvent(QMouseEvent *e) {
  if (!axes.shown ||
      (axes.selected_axis < 0 && !(e->modifiers() & Qt::ControlModifier)) ||
      !(e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) ||
      !(e->buttons() & Qt::LeftButton))
    return QGLViewer::mouseMoveEvent(e);
  using qglviewer::Vec;
  if (e->modifiers() & Qt::ControlModifier) { // move in screen plane
    double depth = camera()->projectedCoordinatesOf(Vec(axes.position))[2];
    auto pos = camera()->unprojectedCoordinatesOf(Vec(e->pos().x(), e->pos().y(), depth));
    axes.position = toVector(pos);
  } else { ... }
  objects[selected_object]->movement(selected_vertex, axes.position);
  objects[selected_object]->updateBaseMesh();
  updateMeanMinMax(); update();
}

Modification

void Viewer::mouseMoveEvent(QMouseEvent *e) {
  if (!axes.shown ||
      (axes.selected_axis < 0 && !(e->modifiers() & Qt::ControlModifier)) ||
      !(e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) || 
      !(e->buttons() & Qt::LeftButton))
    return QGLViewer::mouseMoveEvent(e);
  using qglviewer::Vec;
  if (...) { ... } else { // move by axis
    Vec from, dir, axis(axes.selected_axis == 0, axes.selected_axis == 1, axes.selected_axis == 2);
    camera()->convertClickToLine(e->pos(), from, dir);
    auto p = intersectLines(axes.grabbed_pos, toVector(axis), toVector(from), toVector(dir));
    float d = (p - axes.grabbed_pos) | toVector(axis);
    axes.position[axes.selected_axis] = axes.original_pos[axes.selected_axis] + d;
  }
  objects[selected_object]->movement(selected_vertex, axes.position);
  objects[selected_object]->updateBaseMesh();
  updateMeanMinMax(); update();
}

Kitérő egyenesek metszése

  • Legközelebbi pontok: és
  • merőleges mindkét egyenesre
    és
  • Kétismeretlenes egyenletrendszer


  • és ,
    ahol , , ,
    ,
  • Nekünk csak a kell

ahol
, , , ,

  • Itt: ap = , ad = és bp = , bd =

static Vector intersectLines(const Vector &ap, const Vector &ad,
                             const Vector &bp, const Vector &bd) {
  // always returns a point on the (ap, ad) line
  double a = ad.sqrnorm(), b = ad | bd, c = bd.sqrnorm();
  double d = ad | (ap - bp), e = bd | (ap - bp);
  if (a * c - b * b < 1.0e-7)
    return ap;
  double s = (b * e - c * d) / (a * c - b * b);
  return ap + s * ad;
}

A selected_object beállítása

  • A default implementáció, picit átírva
void Viewer::endSelection(const QPoint &) {
  glFlush();
  GLint nbHits = glRenderMode(GL_RENDER);
  if (nbHits <= 0)
    setSelectedName(-1);
  else {
    const GLuint *ptr = selectBuffer();
    GLuint zMin = std::numeric_limits<GLuint>::max();
    for (int i = 0; i < nbHits; ++i, ptr += 4) {
      GLuint names = ptr[0];
      if (ptr[1] < zMin) {
        zMin = ptr[1];
        if (names == 2) {
          selected_object = ptr[3];
          ptr++;
        }
        setSelectedName(ptr[3]);
      } else if (names == 2)
        ptr++;
    }
  }
}

Kész a negyedik fázis

  • Hiányosságok:
    • Takart pontok is kiválaszthatóak
    • A tengelyek mérete nem teljesen nézetfüggetlen