Vector Object::normal(BaseMesh::VertexHandle vh) const {
auto v = OpenMesh::make_smart(vh, &mesh);
Vector n(0.0, 0.0, 0.0);
for (auto h : v.incoming_halfedges()) {
if (h.is_boundary())
continue;
auto in_vec = mesh.calc_edge_vector(h);
auto out_vec = mesh.calc_edge_vector(h.next());
double w = in_vec.sqrnorm() * out_vec.sqrnorm();
n += (in_vec % out_vec) / (w == 0.0 ? 1.0 : w);
}
double len = n.length();
if (len != 0.0)
n /= len;
return n;
}
double Object::meanCurvature(BaseMesh::VertexHandle vh) const {
auto v = OpenMesh::make_smart(vh, &mesh);
// Compute triangle strip area
double vertex_area = 0;
for (auto f : v.faces())
vertex_area += mesh.calc_sector_area(f.halfedge());
vertex_area /= 3.0;
// Compute mean values using dihedral angles
double mean = 0;
for (auto h : v.incoming_halfedges()) {
auto vec = mesh.calc_edge_vector(h);
double angle = mesh.calc_dihedral_angle(h); // signed; returns 0 at the boundary
mean += angle * vec.norm();
}
return mean / (4 * vertex_area);
}
calc_dihedral_angle
implementációjaOpenMesh/Core/Mesh/PolyMeshT.hh
alapjánScalar calc_dihedral_angle(HalfedgeHandle he) const {
if (is_boundary(edge_handle(he)))
return 0;
auto n0 = normal(face_handle(he));
auto n1 = normal(face_handle(opposite_halfedge_handle(he)));
auto cos_angle = n0 | n1;
auto sign = (n0 % n1) | calc_edge_vector(he);
return std::acos(std::clamp(cos_angle, -1.0, 1.0)) * (sign < 0 ? -1 : 1);
}
visualization.hh
enum class VisType { PLAIN, MEAN, ISOPHOTES };
struct Visualization {
VisType type;
// ...
double mean_min, mean_max, cutoff_ratio;
static Vector colorMap(double min, double max, double d);
};
visualization.cc
static Vector HSV2RGB(Vector hsv) { // As in Wikipedia
double c = hsv[2] * hsv[1], h = hsv[0] / 60, m = hsv[2] - c;
double x = c * (1 - std::abs(std::fmod(h, 2) - 1));
Vector rgb(m, m, m);
if (h <= 1) return rgb + Vector(c, x, 0);
if (h <= 2) return rgb + Vector(x, c, 0);
if (h <= 3) return rgb + Vector(0, c, x);
if (h <= 4) return rgb + Vector(0, x, c);
if (h <= 5) return rgb + Vector(x, 0, c);
if (h <= 6) return rgb + Vector(c, 0, x);
return rgb;
}
Vector Visualization::colorMap(double min, double max, double d) {
double red = 0, green = 120, blue = 240; // Hue
if (d < 0) {
double alpha = min ? std::min(d / min, 1.0) : 1.0;
return HSV2RGB({green * (1 - alpha) + blue * alpha, 1, 1});
}
double alpha = max ? std::min(d / max, 1.0) : 1.0;
return HSV2RGB({green * (1 - alpha) + red * alpha, 1, 1});
}
viewer.hh
class Viewer : public QGLViewer {
Q_OBJECT
public:
double getCutoffRatio() const;
void setCutoffRatio(double ratio);
double getMeanMin() const;
void setMeanMin(double min);
double getMeanMax() const;
void setMeanMax(double max);
// ...
private:
void updateMeanMinMax();
// ...
};
viewer.cc
double Viewer::getCutoffRatio() const { return vis.cutoff_ratio; }
void Viewer::setCutoffRatio(double ratio) {
vis.cutoff_ratio = ratio;
updateMeanMinMax();
}
double Viewer::getMeanMin() const { return vis.mean_min; }
void Viewer::setMeanMin(double min) { vis.mean_min = min; }
double Viewer::getMeanMax() const { return vis.mean_max; }
void Viewer::setMeanMax(double max) { vis.mean_max = max; }
bool Viewer::open(std::string filename) {
// ...
updateMeanMinMax();
setupCamera();
return true;
}
viewer.cc
(folytatás)void Viewer::updateMeanMinMax() {
std::vector<double> mean;
for (auto o : objects) {
const auto &mesh = o->baseMesh();
for (auto v : mesh.vertices())
mean.push_back(mesh.data(v).mean);
}
size_t n = mean.size();
if (n < 3)
return;
std::sort(mean.begin(), mean.end());
size_t k = (double)n * vis.cutoff_ratio;
vis.mean_min = std::min(mean[k ? k-1 : 0], 0.0);
vis.mean_max = std::max(mean[k ? n-k : n-1], 0.0);
}
void Viewer::keyPressEvent(QKeyEvent *e) {
if (e->modifiers() == Qt::NoModifier)
switch (e->key()) {
case Qt::Key_P:
vis.type = VisType::PLAIN;
update();
break;
case Qt::Key_M:
vis.type = VisType::MEAN;
update();
break;
case Qt::Key_I:
vis.type = VisType::ISOPHOTES;
update();
break;
// ...
default:
QGLViewer::keyPressEvent(e);
} else
QGLViewer::keyPressEvent(e);
}
void Object::draw(const Visualization &vis) const {
// ...
if (vis.show_solid || vis.show_wireframe) {
if (vis.type == VisType::PLAIN)
glColor3d(1.0, 1.0, 1.0);
for (auto f : mesh.faces()) {
glBegin(GL_POLYGON);
for (auto v : f.vertices()) {
if (vis.type == VisType::MEAN)
glColor3dv(vis.colorMap(vis.mean_min, vis.mean_max, mesh.data(v).mean).data());
glNormal3dv(mesh.normal(v).data());
glVertex3dv(mesh.point(v).data());
}
glEnd();
}
}
// ...
}
class Window : public QMainWindow {
// ...
private slots:
void setCutoff();
void setRange();
// ...
};
Window::Window(QApplication *parent) :
QMainWindow(), parent(parent), last_directory(".")
{
// ...
auto cutoffAction = new QAction(tr("Set &cutoff ratio"), this);
cutoffAction->setStatusTip(tr("Set mean map cutoff ratio"));
connect(cutoffAction, &QAction::triggered, this, &Window::setCutoff);
auto rangeAction = new QAction(tr("Set &range"), this);
rangeAction->setStatusTip(tr("Set mean map range"));
connect(rangeAction, &QAction::triggered, this, &Window::setRange);
auto visMenu = menuBar()->addMenu(tr("&Visualization"));
visMenu->addAction(cutoffAction);
visMenu->addAction(rangeAction);
}
Widgetek elhelyezése
QWidget::setLayout
[a legkülső layout]
QLayout::addWidget
, QLayout::addLayout
QBoxLayout
, QGridLayout
, QFormLayout
Jó alapméretek, átméretezés kezelése
Saját widgetekhez sizeHint()
void Window::setCutoff() {
auto dlg = std::make_unique<QDialog>(this);
auto *hb1 = new QHBoxLayout, *hb2 = new QHBoxLayout;
auto *vb = new QVBoxLayout;
auto *text = new QLabel(tr("Cutoff ratio:"));
auto *sb = new QDoubleSpinBox;
auto *cancel = new QPushButton(tr("Cancel")), *ok = new QPushButton(tr("Ok"));
sb->setDecimals(3); sb->setRange(0.001, 0.5);
sb->setSingleStep(0.01); sb->setValue(viewer->getCutoffRatio());
connect(cancel, &QPushButton::pressed, dlg.get(), &QDialog::reject);
connect(ok, &QPushButton::pressed, dlg.get(), &QDialog::accept);
ok->setDefault(true);
hb1->addWidget(text); hb1->addWidget(sb);
hb2->addWidget(cancel); hb2->addWidget(ok);
vb->addLayout(hb1); vb->addLayout(hb2);
dlg->setWindowTitle(tr("Set ratio"));
dlg->setLayout(vb);
if(dlg->exec() == QDialog::Accepted) {
viewer->setCutoffRatio(sb->value());
viewer->update();
}
}
void Window::setRange() {
QDialog dlg(this);
auto *grid = new QGridLayout;
auto *text1 = new QLabel(tr("Min:")), *text2 = new QLabel(tr("Max:"));
auto *sb1 = new QDoubleSpinBox, *sb2 = new QDoubleSpinBox;
auto *cancel = new QPushButton(tr("Cancel")), *ok = new QPushButton(tr("Ok"));
double max = 1000.0; // controls the number of displayable digits
sb1->setDecimals(5); sb2->setDecimals(5);
sb1->setRange(-max, 0.0); sb2->setRange(0.0, max);
sb1->setSingleStep(0.01); sb2->setSingleStep(0.01);
sb1->setValue(viewer->getMeanMin()); sb2->setValue(viewer->getMeanMax());
connect(cancel, &QPushButton::pressed, &dlg, &QDialog::reject);
connect(ok, &QPushButton::pressed, &dlg, &QDialog::accept);
ok->setDefault(true);
grid->addWidget( text1, 1, 1, Qt::AlignRight); grid->addWidget( sb1, 1, 2);
grid->addWidget( text2, 2, 1, Qt::AlignRight); grid->addWidget( sb2, 2, 2);
grid->addWidget(cancel, 3, 1); grid->addWidget( ok, 3, 2);
dlg.setWindowTitle(tr("Set range"));
dlg.setLayout(grid);
if(dlg.exec() == QDialog::Accepted) {
viewer->setMeanMin(sb1->value()); viewer->setMeanMax(sb2->value());
viewer->update();
}
}
Környezeti térkép (environment map)
textures.qrc
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>isophotes.png</file>
</qresource>
</RCC>
:/
elérési útglGenTextures(n, addr)
glBindTexture(type, id)
glDeleteTextures(n, addr)
glTexParameter[if]v?
)
glTexEnv[if]v?
)
glTexImage2D(...sok paraméter...)
visualization.hh
struct Visualization {
static GLuint isophote_texture;
...
};
visualization.cc
GLuint Visualization::isophote_texture;
class Viewer : public QGLViewer {
Q_OBJECT
public:
~Viewer();
// ...
};
Viewer::~Viewer() {
glDeleteTextures(1, &vis.isophote_texture);
}
void Viewer::init() {
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, 1);
QImage img(":/isophotes.png");
glGenTextures(1, &vis.isophote_texture);
glBindTexture(GL_TEXTURE_2D, vis.isophote_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img.width(), img.height(), 0, GL_BGRA,
GL_UNSIGNED_BYTE, img.convertToFormat(QImage::Format_ARGB32).bits());
}
GL_LINEAR
(lineáris interpoláció):
MIN
: távoli felületekMAG
: közeli felületekGL_CLAMP_TO_EDGE
:
glEnable(GL_TEXTURE_2D)
glTexCoord[1234][dfis]v?
glTexGen[dfi]v?
: generálás paraméterei
GL_TEXTURE_GEN_MODE
: mód beállítása
GL_OBJECT_LINEAR
GL_EYE_LINEAR
GL_SPHERE_MAP
GL_OBJECT_PLANE
/ GL_EYE_PLANE
: sík megadásaglEnable(GL_TEXTURE_GEN_S)
, glEnable(GL_TEXTURE_GEN_T)
void Object::draw(const Visualization &vis) const {
// ...
if (vis.show_solid || vis.show_wireframe) {
if (vis.type == VisType::PLAIN)
glColor3d(1.0, 1.0, 1.0);
else if (vis.type == VisType::ISOPHOTES) {
glBindTexture(GL_TEXTURE_2D, vis.isophote_texture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glEnable(GL_TEXTURE_2D);
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
}
// ... (actual drawing) ...
if (vis.type == VisType::ISOPHOTES) {
glDisable(GL_TEXTURE_GEN_S);
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
}
// ...
}