Brush shape selection, clone tool, clone preview window, all original tools now included and many more improvements. Still many bugs to fix and heavy refactoring on some code necessary. GUI incomplete

This commit is contained in:
Andrea Bernabei bernabei 2008-04-15 06:48:58 +00:00
parent eed31a1322
commit ef1ee88bfc
6 changed files with 905 additions and 408 deletions

View File

@ -26,7 +26,6 @@
#include <GL/glew.h>
#include <cmath>
#include <iostream>
#include <cstdlib>
#include <meshlab/glarea.h>
#include <vcg/complex/trimesh/update/bounding.h>
@ -43,8 +42,13 @@ EditPaintPlugin::EditPaintPlugin()
foreach(editAction, actionList)
editAction->setCheckable(true);
zbuffer = NULL;
color_buffer = NULL;
mark = 0;
disable_decorate = false;
circle = generateCircle();
dense_circle = generateCircle(64);
square = generateSquare();
dense_square = generateSquare(16);
}
EditPaintPlugin::~EditPaintPlugin()
@ -84,9 +88,17 @@ void EditPaintPlugin::StartEdit(QAction *, MeshModel& m, GLArea * parent)
tri::UpdateBounding<CMeshO>::Box(m.cm);
m.updateDataMask(MeshModel::MM_VERTFACETOPO);
parent->getCurrentRenderMode().colorMode=vcg::GLW::CMPerVert;
parent->mm()->ioMask|=MeshModel::IOM_VERTCOLOR;
m.updateDataMask(MeshModel::MM_FACEMARK);
if ((m.currentDataMask & MeshModel::IOM_VERTCOLOR) == 0)
{
Color4b color(100, 100, 100, 255);
for (CMeshO::VertexIterator i = m.cm.vert.begin(); i != m.cm.vert.end(); i++) (*i).C() = color;
}
parent->mm()->ioMask|=MeshModel::IOM_VERTQUALITY;
parent->mm()->ioMask|=MeshModel::IOM_VERTCOLOR;
parent->getCurrentRenderMode().colorMode=vcg::GLW::CMPerVert;
QObject::connect(paintbox, SIGNAL(undo()), this, SLOT(update()));
QObject::connect(paintbox, SIGNAL(redo()), this, SLOT(update()));
@ -115,7 +127,7 @@ void EditPaintPlugin::mousePressEvent(QAction * , QMouseEvent * event, MeshModel
gla->update();
}
void EditPaintPlugin::mouseMoveEvent(QAction *, QMouseEvent* event, MeshModel &, GLArea * gla)
void EditPaintPlugin::mouseMoveEvent(QAction *, QMouseEvent* event, MeshModel & m, GLArea * gla)
{
event_queue.enqueue(*event);
gla->update();
@ -127,13 +139,30 @@ void EditPaintPlugin::mouseReleaseEvent (QAction *,QMouseEvent * event, MeshMod
gla->update();
}
void EditPaintPlugin::projectCursor(MeshModel & m, GLArea * gla)
{
if (paintbox->getSizeUnit() == 0){
if (paintbox->getBrush() == CIRCLE) drawSimplePolyLine(gla, cursor, paintbox->getSize(), circle);
if (paintbox->getBrush() == SQUARE) drawSimplePolyLine(gla, cursor, paintbox->getSize(), square);
}
if (paintbox->getSizeUnit() == 1) {
if (paintbox->getBrush() == CIRCLE) drawPercentualPolyLine(gla, gl_cursor, m, zbuffer, modelview_matrix, projection_matrix, viewport, paintbox->getRadius(), dense_circle);
if (paintbox->getBrush() == SQUARE) drawPercentualPolyLine(gla, gl_cursor, m, zbuffer, modelview_matrix, projection_matrix, viewport, paintbox->getRadius(), dense_square);
}
}
/**
* Since only on a Decorate call it is possible to obtain correct values
* from OpenGL, all operations are performed during the execution of this
* method and not where mouse events are processed.
*
* TODO If update() is called from within the loop on events, there will be a "read ahead" of
* events that will disrupt the status
*/
void EditPaintPlugin::Decorate(QAction*, MeshModel &m, GLArea * gla)
{
if (disable_decorate) return;
glarea = gla;
if (event_queue.isEmpty()) return;
@ -157,7 +186,7 @@ void EditPaintPlugin::Decorate(QAction*, MeshModel &m, GLArea * gla)
if (event.type() == QEvent::MouseButtonPress) button = event.button();
prev_cursor = cursor;
prev_cursor = cursor; //TODO what if the cursor was not initialized???
cursor = event.pos();
gl_cursor.setX(cursor.x()); gl_cursor.setY(gla->curSiz.height() - cursor.y());
@ -194,6 +223,7 @@ void EditPaintPlugin::Decorate(QAction*, MeshModel &m, GLArea * gla)
color.setRgb(pixel[0], pixel[1], pixel[2], 255);
(button == Qt::LeftButton) ? paintbox->setForegroundColor(color) : paintbox->setBackgroundColor(color);
}
paintbox->restorePreviousType();
}
continue;
@ -205,37 +235,20 @@ void EditPaintPlugin::Decorate(QAction*, MeshModel &m, GLArea * gla)
gla->update();
}
continue;
case COLOR_CLONE :
if (event.type() == QEvent::MouseButtonPress && button == Qt::RightButton)
{
//store position
}else if (event.type() == QEvent::MouseButtonPress && button == Qt::LeftButton)
{
//inizialize cloning
}else if (event.type() == QEvent::MouseMove && button == Qt::LeftButton)
{
//clone
}else if (event.type() == QEvent::MouseButtonRelease && button == Qt::LeftButton)
{
//end cloning
}
default :
break;
}
if (paintbox->getSizeUnit() == 0) drawSimplePolyLine(gla, cursor, paintbox->getSize(), circle);
if (paintbox->getSizeUnit() == 1) drawPercentualPolyLine(gla, gl_cursor, m, zbuffer, modelview_matrix, projection_matrix, viewport, paintbox->getRadius(), dense_circle);
vector< pair<CVertexO *, float> > * vertices;
vertices = new vector< pair<CVertexO *, float> >(); //TODO don't need vertices for face selection!
updateSelection(m, vertices);
vertices.clear(); //TODO don't need vertices for face selection!
switch (paintbox->getCurrentType())
{
case MESH_SELECT:
projectCursor(m, glarea);
updateSelection(m);
if (event.type() == QEvent::MouseButtonPress)
{
paintbox->getUndoStack()->beginMacro("Select");
@ -252,64 +265,323 @@ void EditPaintPlugin::Decorate(QAction*, MeshModel &m, GLArea * gla)
}
break;
case MESH_PULL :
case MESH_PUSH :
if (event.type() != QEvent::MouseMove) break;
drawNormalPercentualCircle(gla, gl_cursor, m, zbuffer,
modelview_matrix, projection_matrix, viewport, paintbox->getRadius());
case MESH_PULL :
projectCursor(m, glarea);
updateSelection(m, & vertices);
if (event.type() == QEvent::MouseButtonPress) { displaced_vertices.clear(); paintbox->getUndoStack()->beginMacro("Pull"); }
else if (event.type() == QEvent::MouseMove) sculpt(& vertices, 1 / (float) m.cm.bbox.Diag());
else if (event.type() == QEvent::MouseButtonRelease) paintbox->getUndoStack()->endMacro();
break;
case MESH_PUSH :glarea->setLight(false);
projectCursor(m, glarea);
updateSelection(m, & vertices);
if (event.type() == QEvent::MouseButtonPress) { displaced_vertices.clear(); paintbox->getUndoStack()->beginMacro("Push"); }
else if (event.type() == QEvent::MouseMove) sculpt(& vertices, - 1 / (float) m.cm.bbox.Diag());
else if (event.type() == QEvent::MouseButtonRelease) paintbox->getUndoStack()->endMacro();
break;
case COLOR_PAINT :
projectCursor(m, glarea);
updateSelection(m, & vertices);
if (event.type() == QEvent::MouseButtonPress)
{
paintbox->getUndoStack()->beginMacro("Paint");
visited_vertices.clear();
paint(& vertices);
}
else if (event.type() == QEvent::MouseMove) paint(vertices);
else if (event.type() == QEvent::MouseMove) paint(& vertices);
else if (event.type() == QEvent::MouseButtonRelease) paintbox->getUndoStack()->endMacro();
break;
case COLOR_SMOOTH:
case MESH_SMOOTH:
projectCursor(m, glarea);
updateSelection(m, & vertices);
if (event.type() == QEvent::MouseButtonPress)
{
paintbox->getUndoStack()->beginMacro("Smooth");
mark++; //TODO BAD mark usage
}
else if (event.type() == QEvent::MouseMove) smooth(& vertices);
else if (event.type() == QEvent::MouseButtonRelease) paintbox->getUndoStack()->endMacro();
break;
case COLOR_CLONE :
if (event.type() == QEvent::MouseButtonPress && button == Qt::RightButton)
{
if (color_buffer != NULL) delete color_buffer;
disable_decorate = true;
glarea->update();
color_buffer = new GLubyte[gla->curSiz.width()*gla->curSiz.height()*3];
clone_zbuffer = new GLfloat[gla->curSiz.width()*gla->curSiz.height()];
glReadPixels(0,0,gla->curSiz.width(), gla->curSiz.height(), GL_RGB, GL_UNSIGNED_BYTE, color_buffer);
glReadPixels(0,0,gla->curSiz.width(), gla->curSiz.height(), GL_DEPTH_COMPONENT, GL_FLOAT, clone_zbuffer);
start_cursor.setX(gl_cursor.x()); start_cursor.setY(gl_cursor.y()); //storing now source click position
//TODO refactor
QImage image(glarea->width(), glarea->height(), QImage::Format_RGB32);
for (int x = 0; x < glarea->width(); x++){
for (int y = 0; y < glarea->height(); y++){
int index = (y * glarea->width() + x)*3;
image.setPixel(x, glarea->height() - y, qRgb((int)color_buffer[index], (int)color_buffer[index + 1], (int)color_buffer[index + 2]));
}
}
paintbox->setClonePixmap(image);
paintbox->setPixmapCenter(-cursor.x(), -cursor.y());
disable_decorate = false;
}else if (event.type() == QEvent::MouseButtonPress && button == Qt::LeftButton)
{
projectCursor(m, glarea);
updateSelection(m, & vertices);
paintbox->getUndoStack()->beginMacro("Clone");
visited_vertices.clear();
start_cursor.setX(start_cursor.x() - gl_cursor.x()); start_cursor.setY(start_cursor.y() - gl_cursor.y());
}else if (event.type() == QEvent::MouseMove && button == Qt::LeftButton)
{
projectCursor(m, glarea);
updateSelection(m, & vertices);
paintbox->setPixmapCenter(-cursor.x() - start_cursor.x(), -cursor.y() + start_cursor.y() );
paint( & vertices);
}else if (event.type() == QEvent::MouseButtonRelease && button == Qt::LeftButton)
{
projectCursor(m, glarea);
updateSelection(m, & vertices);
paintbox->getUndoStack()->endMacro();
}
break;
case COLOR_NOISE :
//TODO Perlin noise application
default :
break;
}
delete vertices;
}
}
void EditPaintPlugin::paint(vector< pair<CVertexO *, float> > * vertices)
void EditPaintPlugin::smooth(vector< pair<CVertexO *, VertexDistance> > * vertices)
{
QHash <CVertexO *, pair<float[3], Color4b> > originals;
Color4b newcol, destCol;
int opac = paintbox->getOpacity();
int decrease_pos = paintbox->getSmoothPercentual();
int c_r, c_g, c_b;
float p_x, p_y, p_z;
float newpos[3];
for (unsigned int k = 0; k < vertices->size(); k++) //forach selected vertices
{
pair<CVertexO *, VertexDistance> data = vertices->at(k);
CVertexO * v = data.first;
VertexDistance * vd = & data.second;
pair<float[3], Color4b> pc_data; //save its color and position
for (int k = 0; k < 4; k++) pc_data.second[k] = v->C()[k];
for (int k = 0; k < 3; k++) pc_data.first[k] = v->P()[k];
if (v->IMark() != mark)
{
if (paintbox->getCurrentType() == COLOR_SMOOTH) paintbox->getUndoStack()->push(new SingleColorUndo(v, v->C()));
else paintbox->getUndoStack()->push(new SinglePositionUndo(v, v->P()));
v->IMark() = mark;
}
if (!originals.contains(v)) originals.insert(v, pc_data); //save original color/position data
CFaceO * one_face = v->VFp(); //one of the faces adjacent to the vertex
int pos = v->VFi(); //index of vertex v on face one_face
c_r = c_g = c_b = 0;
p_x = p_y = p_z = 0;
int count_me = 0;
CFaceO * f = one_face;
do
{ /// calc the sum of the surrounding vertexes pos or and vertexes color
CFaceO * temp = one_face->VFp(pos); //next face in VF list
if (one_face != 0 && !one_face->IsD())
{
for (int k = 0; k < 3; k++)
{
if (pos != k)
{
CVertexO * v = one_face->V(k);
Color4b tco = v->C();
if (originals.contains(v))
{
pair<float[3], Color4b> pc_data_k = originals[v];
tco = pc_data_k.second;
p_x += pc_data_k.first[0]; p_y += pc_data_k.first[1]; p_z += pc_data_k.first[2];
}else if (paintbox->getCurrentType() == MESH_SMOOTH)
{
p_x += v->P()[0]; p_y += v->P()[1]; p_z += v->P()[2];
}
c_r += tco[0]; c_g += tco[1]; c_b += tco[2];
}
}
pos=one_face->VFi(pos);
count_me+=2;
}
one_face = temp;
} while (one_face != f && one_face != 0);
if (count_me > 0) /// calc the new color or pos
{
float op = brush(paintbox->getBrush(), vd->distance, vd->rel_position.x(), vd->rel_position.y(), decrease_pos);
if (paintbox->getCurrentType() == COLOR_SMOOTH)
{
newcol[0] = c_r/count_me;
newcol[1] = c_g/count_me;
newcol[2] = c_b/count_me;
mergeColors((float)(op*opac)/100.0, newcol, v->C(), &destCol);
v->C()[0] = destCol[0];
v->C()[1] = destCol[1];
v->C()[2] = destCol[2];
} else
{
newpos[0] = p_x/(float)count_me;
newpos[1] = p_y/(float)count_me;
newpos[2] = p_z/(float)count_me;
float po[3]; for (int lauf=0; lauf<3; lauf++) po[lauf]=v->P()[lauf];
mergePositions((float)(op*opac)/100.0,newpos,po,newpos);
for (int lauf=0; lauf<3; lauf++) v->P()[lauf]=newpos[lauf];
}
}
if (paintbox->getCurrentType() == MESH_SMOOTH)
{
updateNormal(v);
}
}
}
//TODO Begin modularization of paint to allow different tools to leverage the same architecture
void EditPaintPlugin::sculpt(vector< pair<CVertexO *, VertexDistance> > * vertices, float s)
{
int opac = paintbox->getOpacity();
int decrease_pos = paintbox->getHardness();
float strength = s * paintbox->getDisplacement() / (float) 100;
Point3f normal(0.0, 0.0, 0.0);
//TODO Normal should be calculated during updateSelection
for (unsigned int k = 0; k < vertices->size(); k++)
{
normal += vertices->at(k).first->N() / vertices->size();
}
for (unsigned int k = 0; k < vertices->size(); k++)
{
pair<CVertexO *, VertexDistance> data = vertices->at(k);
float op = brush(paintbox->getBrush(), data.second.distance, data.second.rel_position.x(), data.second.rel_position.y(), decrease_pos);
if (!displaced_vertices.contains(data.first))
{
displaced_vertices.insert(data.first, pair<Point3f, float>(
Point3f(data.first->P()[0], data.first->P()[1], data.first->P()[2]),
opac * op) );
paintbox->getUndoStack()->push(new SinglePositionUndo(data.first, data.first->P()));
displaceAlongVector(data.first, normal, (opac * op) * strength);
} else if (displaced_vertices[data.first].second < opac * op)
{
displaced_vertices[data.first].second = opac * op;
Point3f temp = displaced_vertices[data.first].first;
data.first->P()[0]=temp[0]; data.first->P()[1]=temp[1]; data.first->P()[2]=temp[2];
displaceAlongVector(data.first, normal, (opac * op)* strength);
}
}
for (unsigned int k = 0; k < vertices->size(); k++) updateNormal(vertices->at(k).first);
}
/**
* Painting and Cloning
*/
void EditPaintPlugin::paint(vector< pair<CVertexO *, VertexDistance> > * vertices)
{
int opac = paintbox->getOpacity();
int decrease_pos = paintbox->getHardness();
QColor newcol = (button == Qt::LeftButton) ? paintbox->getForegroundColor() : paintbox->getBackgroundColor();
Color4b color(newcol.red(), newcol.green(), newcol.blue(), newcol.alpha());
int index = 0;
Color4b color;
if (paintbox->getCurrentType() == COLOR_PAINT){
QColor newcol = (button == Qt::LeftButton) ? paintbox->getForegroundColor() : paintbox->getBackgroundColor();
color[0] = newcol.red(); color[1] = newcol.green(); color[2] = newcol.blue(); color[3] = newcol.alpha();
}
for (unsigned int k = 0; k < vertices->size(); k++)
{
pair<CVertexO *, float> data = vertices->at(k);
pair<CVertexO *, VertexDistance> data = vertices->at(k);
float op = brush(paintbox->getBrush(), data.second.distance, data.second.rel_position.x(), data.second.rel_position.y(), decrease_pos);
float op = 0.0;
if (data.second * 100.0 > (float)decrease_pos)
{
op = (data.second * 100.0 - (float)decrease_pos)/ (float) (100 - decrease_pos);
}
if (!visited_vertices.contains(data.first))
{
visited_vertices.insert(data.first, pair<Color4b, float>(
Color4b(data.first->C()[0], data.first->C()[1], data.first->C()[2], data.first->C()[3]),
opac-op*opac) );
op*opac) );
paintbox->getUndoStack()->push(new SingleColorUndo(data.first, data.first->C()));
applyColor(data.first, color, (int)( opac - op * opac) );
} else if (visited_vertices[data.first].second < opac - op * opac)
if (paintbox->getCurrentType() == COLOR_CLONE)
{
index = ((data.second.position.y() + start_cursor.y()) * viewport[2] +
data.second.position.x() + start_cursor.x())*3;
//TODO Optimize the division by three
if (index < glarea->curSiz.width()*glarea->curSiz.height()*3 - 3 && index > 0)
{
if (clone_zbuffer[index / 3] < 1.0){
color[0] = color_buffer[index]; color[1] = color_buffer[index + 1]; color[2] = color_buffer[index + 2];
applyColor(data.first, color, (int)(op * opac));
}
}
}else applyColor(data.first, color, (int)(op * opac) );
} else if (visited_vertices[data.first].second < op * opac)
{
visited_vertices[data.first].second = opac - op * opac;
visited_vertices[data.first].second = op * opac;
Color4b temp = visited_vertices[data.first].first;
data.first->C()[0]=temp[0]; data.first->C()[1]=temp[1]; data.first->C()[2]=temp[2];
applyColor(data.first, color, (int)(opac - op * opac ));
if (paintbox->getCurrentType() == COLOR_CLONE) //TODO refactor in a method
{
index = ((data.second.position.y() + start_cursor.y()) * viewport[2] +
data.second.position.x() + start_cursor.x())*3;
if (index < glarea->curSiz.width()*glarea->curSiz.height()*3 - 3 && index > 0)
{
if (clone_zbuffer[index / 3] < 1.0){
color[0] = color_buffer[index]; color[1] = color_buffer[index + 1]; color[2] = color_buffer[index + 2];
applyColor(data.first, color, (int)(op * opac));
}
}
}else applyColor(data.first, color, (int)(op * opac) );
}
}
}
@ -332,7 +604,7 @@ void EditPaintPlugin::fill(MeshModel & ,CFaceO * face)
Color4b color(newcol.red(), newcol.green(), newcol.blue(), newcol.alpha());
std::cout << newcol.red() <<" "<< newcol.green() <<" "<< newcol.blue() << std::endl;
// std::cout << newcol.red() <<" "<< newcol.green() <<" "<< newcol.blue() << std::endl;
paintbox->getUndoStack()->beginMacro("Fill Color");
@ -408,7 +680,6 @@ void EditPaintPlugin::gradient(MeshModel & m,GLArea * gla) {
if (gradient_form==0) {
double r=(dx-p0.x())*(p1.x()-p0.x())+(dy-p0.y())*(p1.y()-p0.y());
//r=r/(l*l);
r=r/l_square;
float px=p0.x()+r*(p1.x()-p0.x());
@ -418,7 +689,7 @@ void EditPaintPlugin::gradient(MeshModel & m,GLArea * gla) {
py=py-dy;
if (gradient_type==0) {
if (r>=0 && r<=1 /*&& (px*px+py*py<radius*radius)*/) {
if (r>=0 && r<=1) {
mergeColors(r,c1,c2,&merger);
applyColor(vertex,merger,opac);
} else if (r>1) {
@ -427,13 +698,10 @@ void EditPaintPlugin::gradient(MeshModel & m,GLArea * gla) {
applyColor(vertex,c2,opac);
}
} else {
if (r>=0 && r<=1 /*&& (px*px+py*py<radius*radius)*/) {
//mergeColor(r,c1,c2,&merger);
if (r>=0 && r<=1) {
applyColor(vertex,c1,(int)((opac * 0.01) * r * 100.0));
} else if (r>1) {
applyColor(vertex,c1,opac);
} else if (r<0) {
//colorize((*fi).V(lauf),c2,opac);
}
}
} else {
@ -455,19 +723,27 @@ void EditPaintPlugin::gradient(MeshModel & m,GLArea * gla) {
paintbox->getUndoStack()->endMacro();
}
/**
* Updates currently selected faces by using one of the
* two methods. Just a new version of computeBrushedFaces
*
* It's inlined because it's only called once, inside Decorate.
* Should it be called in other places, the inlining must be removed!
*/
void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair<CVertexO *, float> > * vertex_result)
inline void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair<CVertexO *, VertexDistance> > * vertex_result)
{
QHash <CFaceO *,CFaceO *> visited_faces; /*< */
QHash <CVertexO *,CVertexO *> visited_vertices;
vector<CMeshO::FacePointer>::iterator fpi;
vector<CMeshO::FacePointer> temp;
vector<CMeshO::FacePointer> temp; //TODO maybe temp can be placed inside the class for better performance
vector <CFaceO *> surround; /*< surrounding faces of a given face*/
surround.reserve(6);
mark++;
if (selection->size() == 0) {
CMeshO::FaceIterator fi;
temp.reserve(m.cm.fn); //avoid unnecessary expansions
for(fi = m.cm.face.begin(); fi != m.cm.face.end(); ++fi){
if(!(*fi).IsD()) {
temp.push_back((&*fi));
@ -475,6 +751,8 @@ void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair<CVertexO *, flo
}
} else
{
temp.reserve(selection->size()); //avoid unnecessary expansions
vertex_result->reserve(selection->size());
for(fpi = selection->begin();fpi != selection->end(); ++fpi)
{
temp.push_back(*fpi);
@ -492,6 +770,8 @@ void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair<CVertexO *, flo
bool backface = paintbox->getPaintBackFace();
bool invisible = paintbox->getPaintInvisible();
bool percentual = paintbox->getSizeUnit() == 1;
float pen_radius = paintbox->getRadius();
int pen_size = paintbox->getSize();
double EPSILON = 0.003;
@ -517,7 +797,7 @@ void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair<CVertexO *, flo
{
CFaceO * fac = temp.at(k);
bool intern = false;
int checkable = 0;
int checkable = 0; /*< counts how many valid vertices there are in this face*/
for (int i = 0; i < 3; i++) //for each vertex defining the face
{
@ -550,22 +830,24 @@ void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair<CVertexO *, flo
{
for (int j=0; j<3; j++) if (invisible || (z[j].x() <= z[j].y() + EPSILON))
{
float dist;
float radius = percentual ? (paintbox->getRadius() * scale_fac * viewport[3] * fov / distance[j]) : paintbox->getSize();
VertexDistance vd; //TODO make it a pointer
float radius = percentual ? (pen_radius * scale_fac * viewport[3] * fov / distance[j]) : pen_size;
if (isIn(gl_cursorf, gl_prev_cursorf, p[j].x(), p[j].y(), radius , &dist))
if (isIn(gl_cursorf, gl_prev_cursorf, p[j].x(), p[j].y(), radius , & vd.distance, vd.rel_position))
{
intern = true;
if (vertex_result == NULL) continue;
else if(!visited_vertices.contains(fac->V(j)))
else if (fac->V(j)->IMark() != mark)
{
pair<CVertexO *, float> data(fac->V(j), dist);
vd.position.setX((int)p[j].x()); vd.position.setY((int)p[j].y());
pair<CVertexO *, VertexDistance> data(fac->V(j), vd);
vertex_result->push_back(data);
visited_vertices.insert(fac->V(j),fac->V(j));
fac->V(j)->IMark() = mark;
}
}
QPointF pos_res;
if (vertex_result == NULL && !intern && lineHitsCircle(p[j],p[(j+1)%3], gl_cursorf, radius, &pos_res))
if (vertex_result == NULL && !intern && lineHitsCircle(p[j],p[(j+1)%3], gl_cursorf, radius))
{
intern = true;
continue;
@ -578,18 +860,18 @@ void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair<CVertexO *, flo
}
}
if (intern && !visited_faces.contains(fac))
if (intern && fac->IMark() != mark)
{
visited_faces.insert((fac),(fac));
fac->IMark() = mark;
selection->push_back(fac);
vector <CFaceO *> surround;
surround.clear();
for (int lauf=0; lauf<3; lauf++) getSurroundingFacesVF(fac,lauf,&surround);
for (unsigned int lauf3=0; lauf3<surround.size(); lauf3++) {
if (!visited_faces.contains(surround[lauf3]))
for (unsigned int lauf3=0; lauf3<surround.size(); lauf3++)
{
if (surround[lauf3]->IMark() != mark)
{
temp.push_back(surround[lauf3]);
temp.push_back(surround[lauf3]);
}
}
}
@ -610,6 +892,7 @@ void EditPaintPlugin::update()
Q_EXPORT_PLUGIN(EditPaintPlugin)
/*********OpenGL Drawing Routines************/
/**
@ -680,15 +963,15 @@ void drawNormalPercentualCircle(GLArea * , QPoint &glcur, MeshModel &m, GLfloat*
glReadPixels((int)sx, (int)sy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &_z); sz = _z;
std::cout << "2D: " << sx << " " << sy << " " << sz << std::endl;
// std::cout << "2D: " << sx << " " << sy << " " << sz << std::endl;
if (GL_FALSE == gluUnProject(sx, sy, sz, mvmatrix, prmatrix, vmatrix, &x, &y, &z)) std::cout << "false";
// if (GL_FALSE == gluUnProject(sx, sy, sz, mvmatrix, prmatrix, vmatrix, &x, &y, &z)) std::cout << "false";
std::cout << "3D: " << x << " " << y << " " << z << std::endl;
// std::cout << "3D: " << x << " " << y << " " << z << std::endl;
gluProject(x, y, z, mvmatrix, prmatrix, vmatrix, &sx, &sy, &sz);
std::cout << "re2D: " << sx << " " << sy << " " << sz << std::endl;
// std::cout << "re2D: " << sx << " " << sy << " " << sz << std::endl;
glPushAttrib(GL_COLOR_BUFFER_BIT); //---Begin of Point Drawing
glDisable(GL_DEPTH_TEST);
@ -714,7 +997,7 @@ void drawNormalPercentualCircle(GLArea * , QPoint &glcur, MeshModel &m, GLfloat*
normal = fp->N();
MULT /= normal.Norm();
tx = x + MULT * normal.Ext(0); ty = y + MULT * normal.Ext(1); tz = z + MULT * normal.Ext(2);
std::cout << "displaced point: " << tx << " " << ty << " " << tz << std::endl;
// std::cout << "displaced point: " << tx << " " << ty << " " << tz << std::endl;
glPushAttrib(GL_COLOR_BUFFER_BIT); //---Begin of Point Drawing
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
@ -728,51 +1011,13 @@ void drawNormalPercentualCircle(GLArea * , QPoint &glcur, MeshModel &m, GLfloat*
glEnable(GL_DEPTH_TEST);
glPopAttrib(); //---End of Point Drawing
//TODO push matrici
/* glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,gla->curSiz.width(),gla->curSiz.height(),0,-1,1); //TODO troppo
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
gluLookAt(tx, ty, tz, x, y, z, 0, 1, 0);
//gluUnProject() di una circonferenza centrata a metà schermo e salva i punti
GLdouble cx, cy; //centro della circonferenza
GLdouble xs[4]; //x dei punti
GLdouble ys[4]; //y dei punti
GLdouble rad;
cx = gla->curSiz.width()/2; cy = gla->curSiz.height()/2;
// xs[0] = cx + rad; ys[0] = cy;
// xs[1] = cx; ys[1] = cy + rad;
// xs[2] = cx - rad; ys[2] = cy;
// xs[3] = cx; ys[3] = cy - rad;
// GLUquadric glq();
// gluDisk(glq, 10, 11, 16, 1);
//TODO algoritmo basturdo:
//Proietta linee che partono
//Bresenham line rastering beginning from normal apex going through circumpherence points
//OR a quicktime approach to the problem. (line length == bb.diag)
//TODO think about it
//TODO pop matrici
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
*/
//gluProject() dei punti della circonferenza calcolati sopra
}else
{
std::cout << "face doesn't have normal!" <<std::endl;
// std::cout << "face doesn't have normal!" <<std::endl;
}
}else
{
std::cout << "couldn't pick face!" <<std::endl;
// std::cout << "couldn't pick face!" <<std::endl;
}
@ -782,7 +1027,7 @@ void drawNormalPercentualCircle(GLArea * , QPoint &glcur, MeshModel &m, GLfloat*
if (getVertexAtMouse(m, vp, glcur, mvmatrix, prmatrix, vmatrix)){
// glColor3f(1.0f, 0.0f, 1.0f);
// drawVertex(vp);
std::cout << "vertex: " << (*vp).P()[0] << " " << (*vp).P()[1] << " " << (*vp).P()[2] << std::endl;
// std::cout << "vertex: " << (*vp).P()[0] << " " << (*vp).P()[1] << " " << (*vp).P()[2] << std::endl;
glPushAttrib(GL_COLOR_BUFFER_BIT);
glDisable(GL_DEPTH_TEST);
@ -800,7 +1045,7 @@ void drawNormalPercentualCircle(GLArea * , QPoint &glcur, MeshModel &m, GLfloat*
// std::cout << "vertex*inv_matrix3D: " << x << " " << y << " " << z << std::endl;
gluProject((*vp).P()[0], (*vp).P()[1], (*vp).P()[2], mvmatrix, prmatrix, vmatrix, &sx, &sy, &sz);
std::cout << "vertex2D: " << (int)sx << " " << (int)sy << " " << sz << std::endl;
// std::cout << "vertex2D: " << (int)sx << " " << (int)sy << " " << sz << std::endl;
}
glPopAttrib();
@ -968,23 +1213,50 @@ void drawPercentualPolyLine(GLArea * gla, QPoint &mid, MeshModel &m, GLfloat* pi
delete proj_points;
}
//each side is divided in given segments
vector<QPointF> * generatePolygon(int sides, int segments)
{
vector<QPointF> * temp = new vector<QPointF>();
float step = sides / 2.0;
float start_angle = M_PI / sides;
for (int k = 0; k < sides; k++)
{
temp->push_back(QPointF(sin((M_PI * (float)k / step) + start_angle), cos((M_PI * (float)k / step) + start_angle)));
}
if (segments > 1)
{
vector<QPointF> * result = new vector<QPointF>();
int sk;
for (int k = 0; k < sides; k++ )
{
sk = (k + 1) % sides;
QPointF kv = temp->at(k);
QPointF skv = temp->at(sk);
QPointF d = (skv - kv)/segments;
result->push_back(kv);
for (int j = 1; j < segments; j++)
{
result->push_back(kv + d * j);
}
}
return result;
}else return temp;
}
/**
* Generates the same circle points that Gfrei's algorithm does
* With the same procedure too.
*/
vector<QPointF> * generateCircle(int segments)
{
vector<QPointF> * result = new vector<QPointF>();
float step = segments / 2.0;
for (int k = 0; k < segments; k++) result->push_back(QPointF(sin(M_PI * (float)k / step), cos(M_PI * (float)k / step)));
return result;
return generatePolygon(segments, 1);
}
vector<QPointF> * generateSquare()
vector<QPointF> * generateSquare(int segments)
{
return generateCircle(4);
return generatePolygon(4, segments);
}

View File

@ -62,18 +62,25 @@ public:
virtual void mousePressEvent (QAction *, QMouseEvent *event, MeshModel &/*m*/, GLArea * );
virtual void mouseMoveEvent (QAction *,QMouseEvent *event, MeshModel &/*m*/, GLArea * );
virtual void mouseReleaseEvent (QAction *,QMouseEvent *event, MeshModel &/*m*/, GLArea * );
// virtual void tabletEvent (QAction *, QTabletEvent *, MeshModel & , GLArea *); //TODO Make them come! glarea
virtual QList<QAction *> actions() const ;
public slots:
void update();
private:
void updateSelection(MeshModel &m, std::vector< std::pair<CVertexO *, float> > * vertex_result = NULL);
struct VertexDistance { QPoint position; QPointF rel_position; float distance;};
void paint(std::vector< std::pair<CVertexO *, float> > * vertices);
void updateSelection(MeshModel &m, vector< pair<CVertexO *, VertexDistance> > * vertex_result = NULL);
void paint(vector< pair<CVertexO *, VertexDistance> > * vertices);
void sculpt(vector< pair<CVertexO *, VertexDistance> > * vertices, float strength);
void smooth(vector< pair<CVertexO *, VertexDistance> > * vertices);
void fill(MeshModel & m,CFaceO * face);
void gradient(MeshModel & m,GLArea * gla);
void projectCursor(MeshModel & m, GLArea * gla);
QPoint cursor; /*< indicates the last known cursor position (for now) */
QPoint gl_cursor; //same cursor but in OpenGl coordinates
QPoint start_cursor; //indicates last position at mouse click
@ -84,8 +91,11 @@ private:
GLint viewport[4];
GLfloat* zbuffer; /*< last known zbuffer, always kept up-to-date */
Qt::MouseButton button;
GLubyte* color_buffer; /*< buffer used as color source in cloning*/
GLfloat* clone_zbuffer; /*<buffer to determine if the source is legal or not */
Qt::MouseButton button; //TODO Check its usefulness
QQueue<QMouseEvent> event_queue; /*< Queue used to store UI events in order to postpone their processing during a Decorate call*/
@ -94,11 +104,19 @@ private:
GLArea * glarea; /*< current glarea */
std::vector<CMeshO::FacePointer> * selection; //currently selected faces
QHash<CVertexO *, std::pair<vcg::Color4b, float> > visited_vertices; //active vertices during paint
vector<CMeshO::FacePointer> * selection; //currently selected faces
vector< pair<CVertexO *, VertexDistance> > vertices; //touched vertices during last updateSelection
QHash<CVertexO *, pair<Color4b, float> > visited_vertices; //active vertices during painting
QHash<CVertexO *, pair<Point3f, float> > displaced_vertices; //active vertices during sculpting
std::vector<QPointF> * circle;
std::vector<QPointF> * dense_circle;
bool disable_decorate;
int mark;
vector<QPointF> * circle;
vector<QPointF> * dense_circle;
vector<QPointF> * square;
vector<QPointF> * dense_square;
};
/**
@ -111,19 +129,39 @@ class SingleColorUndo : public QUndoCommand
{
public:
SingleColorUndo(CVertexO * v, vcg::Color4b c, QUndoCommand * parent = 0) : QUndoCommand(parent){
SingleColorUndo(CVertexO * v, Color4b c, QUndoCommand * parent = 0) : QUndoCommand(parent){
vertex = v; original = c; // setText("Single Vertex Color Change");
}
virtual void undo() { vcg::Color4b temp = vertex->C(); vertex->C() = original; original = temp;}
virtual void undo() {Color4b temp = vertex->C(); vertex->C() = original; original = temp;}
virtual void redo() {undo();}
virtual int id() {return COLOR_PAINT;}
private:
CVertexO* vertex;
vcg::Color4b original;
Color4b original;
};
//TODO Create MultipleColorUndo
class SinglePositionUndo : public QUndoCommand
{
public:
SinglePositionUndo(CVertexO * v, Point3f p, QUndoCommand * parent = 0) : QUndoCommand(parent){
vertex = v; original = p;
}
virtual void undo() {Point3f temp = vertex->P(); vertex->P() = original; original = temp;}
virtual void redo() {undo();}
virtual int id() {return MESH_PULL;}
private:
CVertexO* vertex;
Point3f original;
};
/**
* Undoes changes in selected faces.
*
@ -154,12 +192,12 @@ private :
*
* O(1) complexity
*/
inline void applyColor(CVertexO * vertex, const vcg::Color4b& newcol, int opac)
inline void applyColor(CVertexO * vertex, const Color4b& newcol, int opac)
{
vcg::Color4b orig = vertex->C();
Color4b orig = vertex->C();
for (int i = 0; i < 4; i ++)
orig[i] = std::min(255,( (newcol[i]-orig[i])*opac + orig[i]*(100) )/100);
orig[i] = min(255,( (newcol[i]-orig[i])*opac + orig[i]*(100) )/100);
vertex->C() = orig;
}
@ -169,10 +207,10 @@ inline void applyColor(CVertexO * vertex, const vcg::Color4b& newcol, int opac)
*
* O(1) complexity
*/
inline void mergeColors(double percent,const vcg::Color4b& c1,const vcg::Color4b& c2, vcg::Color4b* dest)
inline void mergeColors(double percent,const Color4b& c1,const Color4b& c2,Color4b* dest)
{
for (int i = 0; i < 4; i ++)
(*dest)[i] = (char)std::min(255.0,((c1[i]-c2[i])*percent+c2[i]));
(*dest)[i] = (char)min(255.0,((c1[i]-c2[i])*percent+c2[i]));
}
/**
@ -231,7 +269,7 @@ inline int getNearest(QPointF center, QPointF *points, int num)
}
/**
* Checks if a point (dx, dy) is contained in either:
* Checks if a point (x, y) is contained in either:
*
* a) the rectangle aligned to the segment connecting p0 to p1
* whose height is the distance between p0 and p1 and width
@ -240,7 +278,7 @@ inline int getNearest(QPointF center, QPointF *points, int num)
* c) the circle centered in p1 with same radius
*
* To check condition (a): let v be the vector going from p0 to p1
* and w the vector from p0 to (dx, dy). If theta is the angle between
* and w the vector from p0 to (x, y). If theta is the angle between
* v and w, then:
*
* |v| |w| cos(theta) |w|
@ -251,36 +289,42 @@ inline int getNearest(QPointF center, QPointF *points, int num)
* of vector w on vector v.
*
* Since w' = v * r is the projection of w on v, pf = p0 + w' is
* the orthogonal projection of (dx, dy) on the segment
* connecting p0 to p1. If the distance between (dx, dy) and pf is less
* than radius, then (dx, dy) lays inside the rectangle
* the orthogonal projection of (x, y) on the segment
* connecting p0 to p1. If the distance between (x, y) and pf is less
* than radius, then (x, y) lays inside the rectangle
*
*/
inline bool isIn(const QPointF &p0,const QPointF &p1,float dx,float dy,float radius,float *dist)
inline bool isIn(const QPointF &p0,const QPointF &p1, float x,float y,float radius, float *dist, QPointF &pos)
{
if (p0 != p1) //(a) condition needs not to be tested: rectangle is null
float radius_sq = radius * radius;
if (p0 != p1) //otherwise condition (a) needs not to be tested: rectangle is null
{
float v_x = (p1.x()-p0.x());
float v_y = (p1.y()-p0.y());
float v_length_squared = v_x * v_x + v_y * v_y;
float w_x = dx - p0.x();
float w_y = dy - p0.y();
float w_x = x - p0.x();
float w_y = y - p0.y();
float prod = w_x * v_x + w_y * v_y; //scalar product of v and w
float v_dot_w = w_x * v_x + w_y * v_y; //scalar product of v and w
float r = prod / v_length_squared;
float r = v_dot_w / v_length_squared;
float pf_x = p0.x() + r * v_x;
float pf_y = p0.y() + r * v_y;
float delta_x = pf_x - dx;
float delta_y = pf_y - dy;
float delta_x = x - pf_x;
float delta_y = y - pf_y;
if (r >= 0 && r <= 1 && (delta_x * delta_x + delta_y * delta_y < radius * radius))
if (r >= 0 && r <= 1 && (delta_x * delta_x + delta_y * delta_y < radius_sq))
{
*dist = sqrt(delta_x * delta_x + delta_y * delta_y)/radius;
float delta_len = sqrt(delta_x * delta_x + delta_y * delta_y);
*dist = delta_len / radius;
pos.setY(delta_y / radius);
pos.setX(delta_x / radius);
return true;
}
}
@ -288,21 +332,43 @@ inline bool isIn(const QPointF &p0,const QPointF &p1,float dx,float dy,float rad
// there could be some problem when point is nearer p0 or p1 and viceversa
// so i have to check both. is only needed with smooth_borders
bool found = false;
float x1=(dx-p1.x());
float y1=(dy-p1.y());
float bla1=x1*x1+y1*y1;
if (bla1<radius*radius) { *dist=sqrt(bla1)/radius; found = true; }
if (p0 == p1) return found;
float x0=(dx-p0.x());
float y0=(dy-p0.y());
float bla0=x0*x0+y0*y0;
if (bla0<radius*radius) {
if (found) *dist=std::min((*dist),(float)sqrt(bla0)/radius);
else *dist=sqrt(bla0)/radius;
return true;
float dx=(x-p1.x());
float dy=(y-p1.y());
float delta_len_sq =dx*dx+dy*dy;
if (delta_len_sq < radius_sq)
{
float delta_len = sqrt(delta_len_sq);
*dist = delta_len;
pos.setY(dy / radius);
pos.setX(dx / radius);
found = true;
}
if (p0 == p1){
*dist /= radius;
return found;
}
dx=(x-p0.x());
dy=(y-p0.y());
delta_len_sq =dx*dx+dy*dy;
if (delta_len_sq < radius_sq)
{
float delta_len = sqrt(delta_len_sq);
if ( (found && delta_len < pos.x() ) || !found)
{
*dist = delta_len;
// pos.setY(x / delta_len );
pos.setY(dy / radius);
pos.setX(dx / radius);
}
found = true;
}
*dist /= radius;
return found;
}
@ -351,7 +417,7 @@ inline bool isFront(const QPointF &a,const QPointF &b,const QPointF &c) {
* Let L(t) = LineStart + (t * Direction) the parametric equation
* of the line, with 0 <= t <= 1 and Dircetion = LineEnd - LineStart
*
* Let |P - CircleCenter|² = radius² the equation of the circle. Then, substituting L(t) to P
* Let |P - CircleCenter|² = radius² be the equation of the circle. Then, substituting L(t) to P
*
* |LineStart + (t * Direction) - CircleCenter|² = radius²
*
@ -365,17 +431,14 @@ inline bool isFront(const QPointF &a,const QPointF &b,const QPointF &c) {
*
* if d < 0 there are no intersection, if d = 0 there is one, if d > 0 there are two.
*/
inline bool lineHitsCircle(QPointF& LineStart,QPointF& LineEnd,QPointF& CircleCenter, float radius, QPointF* const pOut = 0)
inline bool lineHitsCircle(QPointF& LineStart,QPointF& LineEnd,QPointF& CircleCenter, float radius)
{
const float radius_sq = radius * radius;
QPointF Delta = LineStart - CircleCenter;
float delta_length_sq = Delta.x()*Delta.x()+Delta.y()*Delta.y();
if(delta_length_sq <= radius_sq) { /// startpoint is in circle
if(pOut) *pOut = LineStart;
return true;
}
if(delta_length_sq <= radius_sq) return true;
QPointF Direction = LineEnd - LineStart;
@ -390,18 +453,12 @@ inline bool lineHitsCircle(QPointF& LineStart,QPointF& LineEnd,QPointF& CircleCe
{
const float s = - direction_dot_delta / direction_length_sq;
if(s < 0.0f || s > 1.0f) return false;
else {
if(pOut) *pOut = LineStart + s * Direction;
return true;
}
else return true;
}else //two intersections
{
const float s = (- direction_dot_delta - sqrtf(d)) / direction_length_sq; //only one intersection is chosen
if(s < 0.0f || s > 1.0f) return false;
else {
if(pOut) *pOut = LineStart + s * Direction;
return true;
}
else return true;
}
}
@ -413,9 +470,9 @@ inline bool lineHitsCircle(QPointF& LineStart,QPointF& LineEnd,QPointF& CircleCe
*
* O(1)
*/
inline void displaceAlongNormal(CVertexO* vp, float displacement)
inline void displaceAlongVector(CVertexO* vp, Point3f vector, float displacement)
{
(*vp).P() += vcg::Point3f( ((*vp).N().Ext(0)) * displacement, ((*vp).N().Ext(1)) * displacement, ((*vp).N().Ext(2)) * displacement);
(*vp).P() += vector * displacement;
}
/**
@ -434,7 +491,7 @@ inline bool getVertexAtMouse(MeshModel &m,CMeshO::VertexPointer& value, QPoint&
//TODO e se i vertici sono stati cancellati? (IsD())
if (vcg::GLPickTri<CMeshO>::PickNearestFace(cursor.x(), cursor.y(), m.cm, fp, 2, 2))
if (GLPickTri<CMeshO>::PickNearestFace(cursor.x(), cursor.y(), m.cm, fp, 2, 2))
{
QPointF point[3];
@ -461,7 +518,7 @@ inline bool getVertexAtMouse(MeshModel &m,CMeshO::VertexPointer& value, QPoint&
/**
* calcs the surrounding faces of a vertex with VF topology
*/
inline void getSurroundingFacesVF(CFaceO * fac,int vert_pos, std::vector<CFaceO *> *surround) {
inline void getSurroundingFacesVF(CFaceO * fac,int vert_pos,vector<CFaceO *> *surround) {
CVertexO * vert=fac->V(vert_pos);
int pos=vert->VFi();
CFaceO * first_fac=vert->VFp();
@ -478,21 +535,44 @@ inline void getSurroundingFacesVF(CFaceO * fac,int vert_pos, std::vector<CFaceO
inline bool hasSelected(MeshModel &m) {
CMeshO::FaceIterator fi;
for(fi=m.cm.face.begin();fi!=m.cm.face.end();++fi) {
for(fi=m.cm.face.begin();fi!=m.cm.face.end();fi++) {
if (!(*fi).IsD() && (*fi).IsS()) return true;
}
return false;
}
inline void updateNormal(CVertexO * v)
{
CFaceO * f = v->VFp();
CFaceO * one_face = f;
int pos = v->VFi();
v->N()[0]=0;v->N()[1]=0;v->N()[2]=0;
do {
CFaceO * temp=one_face->VFp(pos);
if (one_face!=0 && !one_face->IsD())
{
for (int lauf=0; lauf<3; lauf++)
if (pos!=lauf) {
v->N()+=one_face->V(lauf)->cN();
}
face::ComputeNormalizedNormal(*one_face);
pos=one_face->VFi(pos);
}
one_face=temp;
} while (one_face!=f && one_face!=0);
v->N().Normalize();
}
/*********OpenGL Drawing Routines************/
void drawNormalPercentualCircle(GLArea *, QPoint &, MeshModel &, GLfloat* , double* , double* , GLint* , float );
void drawVertex(CVertexO* );
void drawLine(GLArea *, QPoint &, QPoint &);
void drawSimplePolyLine(GLArea * gla, QPoint & gl_cur, float scale, std::vector<QPointF> * points);
void drawPercentualPolyLine(GLArea * , QPoint &, MeshModel &, GLfloat* , double* , double* , GLint* , float , std::vector<QPointF> * );
void drawSimplePolyLine(GLArea * gla, QPoint & gl_cur, float scale, vector<QPointF> * points);
void drawPercentualPolyLine(GLArea * , QPoint &, MeshModel &, GLfloat* , double* , double* , GLint* , float , vector<QPointF> * );
std::vector<QPointF> * generateCircle(int segments = 18);
std::vector<QPointF> * generateSquare();
vector<QPointF> * generateCircle(int segments = 18);
vector<QPointF> * generateSquare(int segments = 1);
#endif

View File

@ -25,6 +25,7 @@
<file>images/sculpt-22.png</file>
<file>images/smooth_me.png</file>
<file>images/smudge-22.png</file>
<file>images/square.png</file>
<file>images/swap-colors-12.png</file>
<file>images/switch.png</file>
<file>images/undo-24.png</file>

View File

@ -37,6 +37,24 @@ Paintbox::Paintbox(QWidget * parent, Qt::WindowFlags flags) : QWidget(parent, fl
QPoint p=parent->mapToGlobal(QPoint(0,0));
setGeometry(p.x()+parent->width()-width(),p.y(),width(), 100);
brush_viewer->setScene(new QGraphicsScene());
clone_source_view->setScene(new QGraphicsScene());
item = NULL;
/* QPen pen;
getCloneScene()->addLine((qreal)(clone_source_view->width()/2 ),
(qreal)(clone_source_view->height()/2 + 8),
(qreal)(clone_source_view->width()/2),
(qreal)(clone_source_view->height()/2 - 8), pen)->setZValue(1);
getCloneScene()->addLine((qreal)(clone_source_view->width()/2 + 8),
(qreal)(clone_source_view->height()/2 ),
(qreal)(clone_source_view->width()/2 - 8),
(qreal)(clone_source_view->height()/2), pen)->setZValue(1);
*/
//TODO BRUTTO!!!!!!!!!!!
on_brush_box_currentIndexChanged(0);
}
void Paintbox::setUndoStack(QUndoStack * qus)
@ -46,14 +64,10 @@ void Paintbox::setUndoStack(QUndoStack * qus)
QIcon undo = undo_button->icon();
undo_button->setDefaultAction(stack->createUndoAction(undo_button));
undo_button->defaultAction()->setIcon(undo);
mesh_undo_button->setDefaultAction(stack->createUndoAction(mesh_undo_button));
mesh_undo_button->defaultAction()->setIcon(undo);
QIcon redo = redo_button->icon();
redo_button->setDefaultAction(stack->createRedoAction(redo_button));
redo_button->defaultAction()->setIcon(redo);
mesh_redo_button->setDefaultAction(stack->createRedoAction(mesh_redo_button));
mesh_redo_button->defaultAction()->setIcon(redo);
}
void Paintbox::on_default_colors_clicked()
@ -69,6 +83,20 @@ void Paintbox::on_switch_colors_clicked()
background_frame->setColor(temp);
}
void Paintbox::on_brush_box_currentIndexChanged(int i)
{
if (item != NULL) brush_viewer->scene()->removeItem(item);
item = brush_viewer->scene()->addPixmap(QPixmap::fromImage(
raster(getBrush(), (int) ((brush_viewer->width()-2) * size_slider->value() / 100.0),
(int)((brush_viewer->height()-2) * size_slider->value() / 100.0), getHardness())
)
);
brush_viewer->setSceneRect(item->boundingRect());
}
void Paintbox::setForegroundColor(QColor & c)
{
foreground_frame->setColor(c);

View File

@ -24,15 +24,17 @@
#ifndef PAINTBOX_H_
#define PAINTBOX_H_
#include <vcg/math/base.h>
#include "ui_paintbox.h"
/**
* The types of tools MUST be defined in the same order as
* the buttons inside the tabs.
*/
typedef enum {COLOR_PAINT, COLOR_FILL, COLOR_GRADIENT, COLOR_SMOOTH, COLOR_CLONE, COLOR_PICK,
typedef enum {COLOR_PAINT, COLOR_FILL, COLOR_GRADIENT, COLOR_SMOOTH, COLOR_CLONE, COLOR_PICK, COLOR_NOISE,
MESH_SELECT, MESH_SMOOTH, MESH_PUSH, MESH_PULL} ToolType;
/**
* Mnemonics to address the tabs numbers. These MUST be declared
* in the same order as the actual tabs
@ -41,6 +43,8 @@ enum {COLOR_TAB, MESH_TAB, PREFERENCES_TAB};
//TODO add mnemonics for all settings
typedef enum {CIRCLE, SQUARE, PIXMAP} Brush;
/**
* This class manages the user interface and is concerned
* with emitting appropriate signals. It should not be
@ -53,6 +57,11 @@ class Paintbox : public QWidget, private Ui::Paintbox
private:
ToolType active[2]; //the active tool for each page
QUndoStack * stack;
ToolType previous_type;
QGraphicsPixmapItem * item;
inline int getCurrentTab() {return tabs_container->currentIndex();}
public:
@ -71,9 +80,9 @@ public:
inline int getSizeUnit() {
if (getCurrentTab()==MESH_TAB) return mesh_pen_unit->currentIndex();
else return pen_unit->currentIndex();}
inline QPixmap getBrush(int w, int h) {
if (getCurrentTab()==MESH_TAB) return mesh_brush_box->itemIcon(mesh_brush_box->currentIndex()).pixmap(w, h);
else return brush_box->itemIcon(brush_box->currentIndex()).pixmap(w, h);}
inline Brush getBrush() {
if (getCurrentTab()==MESH_TAB) return (Brush)mesh_brush_box->currentIndex();
else return (Brush)brush_box->currentIndex();}
inline int getSearchMode() { return search_mode->currentIndex() + 1; }
inline int getOpacity() {return opacity_slider->value();}
inline int getSmoothPercentual() {
@ -108,28 +117,114 @@ public:
void setForegroundColor(QColor & c);
void setBackgroundColor(QColor & c);
//TODO Refactor
QGraphicsScene * getCloneScene() {return clone_source_view->scene();}
QGraphicsPixmapItem * getClonePixmap() {return item;}
void setClonePixmap(QImage & image)
{
if (item != NULL) getCloneScene()->removeItem(item);
item = getCloneScene()->addPixmap(QPixmap::fromImage(image));
}
void setPixmapCenter(qreal x, qreal y)
{
item->setPos(x + clone_source_view->width()/2.0, y + clone_source_view->height()/2.0);
}
void restorePreviousType()
{
//TODO Only works as long as types are declared in the same order as buttons
dynamic_cast<QToolButton *>(hboxLayout1->itemAt(previous_type)->widget())->toggle() ;
}
public slots:
void on_pen_button_clicked() {active[COLOR_TAB] = COLOR_PAINT;}
void on_fill_button_clicked() {active[COLOR_TAB] = COLOR_FILL;}
void on_gradient_button_clicked() {active[COLOR_TAB] = COLOR_GRADIENT;}
void on_smooth_button_clicked(){active[COLOR_TAB] = COLOR_SMOOTH;}
void on_clone_button_clicked(){active[COLOR_TAB] = COLOR_CLONE;}
void on_pick_button_clicked(){active[COLOR_TAB] = COLOR_PICK;}
void on_mesh_pick_button_clicked(){active[MESH_TAB] = MESH_SELECT;}
void on_mesh_smooth_button_clicked(){active[MESH_TAB] = MESH_SMOOTH;}
void on_mesh_sculpt_button_clicked(){active[MESH_TAB] = MESH_PUSH;}
void on_mesh_add_button_clicked(){active[MESH_TAB] = MESH_PULL;}
void on_undo_button_clicked(){emit undo();}
void on_redo_button_clicked(){emit redo();}
void on_mesh_undo_button_clicked(){emit undo();}
void on_mesh_redo_button_clicked(){emit redo();}
void on_pen_button_toggled(bool checked) {if(checked) active[COLOR_TAB] = COLOR_PAINT;}
void on_fill_button_toggled(bool checked) {if(checked) active[COLOR_TAB] = COLOR_FILL;}
void on_gradient_button_toggled(bool checked) {if(checked) active[COLOR_TAB] = COLOR_GRADIENT;}
void on_smooth_button_toggled(bool checked){if(checked) active[COLOR_TAB] = COLOR_SMOOTH;}
void on_clone_button_toggled(bool checked){if(checked) active[COLOR_TAB] = COLOR_CLONE;}
void on_pick_button_toggled(bool checked){if(checked) {previous_type = active[COLOR_TAB]; active[COLOR_TAB] = COLOR_PICK;}}
void on_mesh_pick_button_toggled(bool checked){if(checked) active[MESH_TAB] = MESH_SELECT;}
void on_mesh_smooth_button_toggled(bool checked){if(checked) active[MESH_TAB] = MESH_SMOOTH;}
void on_mesh_sculpt_button_toggled(bool checked){if(checked) active[MESH_TAB] = MESH_PUSH;}
void on_mesh_add_button_toggled(bool checked){if(checked) active[MESH_TAB] = MESH_PULL;}
void on_undo_button_toggled(bool checked){if(checked) emit undo();}
void on_redo_button_toggled(bool checked){if(checked) emit redo();}
void on_mesh_undo_button_toggled(bool checked){if(checked) emit undo();}
void on_mesh_redo_button_toggled(bool checked){if(checked) emit redo();}
void on_default_colors_clicked();
void on_switch_colors_clicked();
void on_brush_box_currentIndexChanged(int);
//TODO Brutti!!!
void on_hardness_slider_valueChanged(int i){on_brush_box_currentIndexChanged(i);}
void on_size_slider_valueChanged(int i){on_brush_box_currentIndexChanged(i);}
void on_clone_source_load_button_clicked(){QString s = QFileDialog::getOpenFileName(this,
tr("Open Image"), "", tr("Image Files (*.png *.jpg *.bmp)"));
if (!s.isNull()) {
QPixmap pixmap(s);
getCloneScene()->addPixmap(pixmap);
}
}
signals:
void undo();
void redo();
};
/**
* Returns the "value" of the brush at the given distance from the center
*/
inline float brush(Brush b, float distance, float dx, float dy, float hardness)
{
float op = 0.0; /*< 0 means totally colored, 1 totally blank, but right before returning, its meaning will be the opposite*/
switch (b)
{
case CIRCLE : //circle
if (distance >= 1.0) op = 1.0;
else if (distance * 100.0 > hardness) //if after the treshold of total color
{
op = (distance * 100.0 - hardness)/ (float) (100 - hardness);
}
op = 1 - op;
break;
case SQUARE :
dx = vcg::math::Abs(dx * 141.4213562); dy = vcg::math::Abs(dy * 141.4213562); //multiply by sqrt(2) and by 100
if (dx >= 100 || dy >= 100 ) op = 1.0;
else if (dx > hardness || dy > hardness)
{
op = (vcg::math::Max<float>(dx, dy) - hardness)/ (float) (100 - hardness);
}
op = 1 - op;
break;
case PIXMAP :
default:
break;
}
return op;
}
inline QImage raster(Brush b, int w, int h, float hardness)
{
float cx = w/2.0;
float cy = h/2.0;
QImage image(w, h, QImage::Format_RGB32);
for (float x = 0; x < w; x++){
for (float y = 0; y < h; y++){
float _x = ((x - cx) * 1.4142 * 2)/w;
float _y = ((y - cy) * 1.4142 * 2)/h;
int op = 255 * (1 - brush(b, sqrt(_x * _x + _y * _y), _x, _y, hardness));
image.setPixel(x, y, qRgb(op, op, op));
}
}
return image;
}
#endif /*PAINTBOX_H_*/

View File

@ -18,8 +18,64 @@
<property name="windowTitle" >
<string>Vertex Painting</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<layout class="QVBoxLayout" >
<item>
<widget class="QFrame" name="frame_2" >
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" >
<item>
<widget class="QToolButton" name="undo_button" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>Undo</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/undo-24.png</iconset>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="redo_button" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>Redo</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/redo-24.png</iconset>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabs_container" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
@ -113,7 +169,7 @@
<item>
<widget class="QToolButton" name="smooth_button" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
<property name="text" >
<string>...</string>
@ -441,55 +497,27 @@
</widget>
</item>
<item row="2" column="1" >
<widget class="QFrame" name="undo_frame" >
<widget class="QFrame" name="frame" >
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Raised</enum>
</property>
<widget class="QToolButton" name="redo_button" >
<property name="enabled" >
<bool>false</bool>
</property>
<widget class="QGraphicsView" name="brush_viewer" >
<property name="geometry" >
<rect>
<x>11</x>
<y>47</y>
<width>131</width>
<height>30</height>
<x>34</x>
<y>4</y>
<width>81</width>
<height>81</height>
</rect>
</property>
<property name="text" >
<string>Redo</string>
<property name="verticalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/redo-24.png</iconset>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
<widget class="QToolButton" name="undo_button" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="geometry" >
<rect>
<x>11</x>
<y>11</y>
<width>131</width>
<height>30</height>
</rect>
</property>
<property name="text" >
<string>Undo</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/undo-24.png</iconset>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextBesideIcon</enum>
<property name="horizontalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
</widget>
</widget>
@ -703,7 +731,7 @@ p, li { white-space: pre-wrap; }
<item row="0" column="2" colspan="3" >
<widget class="QComboBox" name="brush_box" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
@ -719,7 +747,20 @@ p, li { white-space: pre-wrap; }
<string>Circle</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/brushes/circle.png</iconset>
<iconset resource="meshlab.qrc" >:/images/circle.png</iconset>
</property>
</item>
<item>
<property name="text" >
<string>Square</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/square.png</iconset>
</property>
</item>
<item>
<property name="text" >
<string>Pixmap</string>
</property>
</item>
</widget>
@ -1050,6 +1091,35 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item row="9" column="0" colspan="2" >
<widget class="QFrame" name="clone_source_frame" >
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QGraphicsView" name="clone_source_view" >
<property name="verticalScrollBarPolicy" >
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="optimizationFlags" >
<enum>QGraphicsView::DontAdjustForAntialiasing</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clone_source_load_button" >
<property name="text" >
<string>Load From File</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="10" column="0" colspan="2" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
@ -1121,7 +1191,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QToolButton" name="mesh_smooth_button" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
<property name="text" >
<string>...</string>
@ -1156,7 +1226,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QToolButton" name="mesh_add_button" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
<property name="text" >
<string>...</string>
@ -1195,68 +1265,6 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="mesh_undo_frame" >
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" >
<item>
<widget class="QToolButton" name="mesh_undo_button" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection" >
<enum>Qt::RightToLeft</enum>
</property>
<property name="text" >
<string>Undo</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/undo-24.png</iconset>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="mesh_redo_button" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection" >
<enum>Qt::LeftToRight</enum>
</property>
<property name="text" >
<string>Redo</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/redo-24.png</iconset>
</property>
<property name="toolButtonStyle" >
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="mesh_smooth_frame" >
<property name="frameShape" >
@ -1364,7 +1372,20 @@ p, li { white-space: pre-wrap; }
<string>Circle</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/brushes/circle.png</iconset>
<iconset resource="meshlab.qrc" >:/images/circle.png</iconset>
</property>
</item>
<item>
<property name="text" >
<string>Square</string>
</property>
<property name="icon" >
<iconset resource="meshlab.qrc" >:/images/square.png</iconset>
</property>
</item>
<item>
<property name="text" >
<string>Pixmap</string>
</property>
</item>
</widget>
@ -1518,7 +1539,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QFrame" name="mesh_displacement_frame" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
@ -1714,12 +1735,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>204</x>
<y>177</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>183</y>
</hint>
</hints>
</connection>
@ -1730,12 +1751,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>228</x>
<y>264</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>270</y>
</hint>
</hints>
</connection>
@ -1746,12 +1767,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>222</x>
<y>413</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>419</y>
</hint>
</hints>
</connection>
@ -1762,12 +1783,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>419</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>222</x>
<y>413</y>
</hint>
</hints>
</connection>
@ -1778,12 +1799,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>183</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>204</x>
<y>177</y>
</hint>
</hints>
</connection>
@ -1798,8 +1819,8 @@ p, li { white-space: pre-wrap; }
<y>199</y>
</hint>
<hint type="destinationlabel" >
<x>263</x>
<y>199</y>
<x>322</x>
<y>215</y>
</hint>
</hints>
</connection>
@ -1814,8 +1835,8 @@ p, li { white-space: pre-wrap; }
<y>197</y>
</hint>
<hint type="destinationlabel" >
<x>247</x>
<y>197</y>
<x>261</x>
<y>212</y>
</hint>
</hints>
</connection>
@ -1826,12 +1847,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>228</x>
<y>297</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>303</y>
</hint>
</hints>
</connection>
@ -1842,12 +1863,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>303</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>228</x>
<y>297</y>
</hint>
</hints>
</connection>
@ -1858,12 +1879,12 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>322</x>
<y>270</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>228</x>
<y>264</y>
</hint>
</hints>
</connection>
@ -1878,8 +1899,8 @@ p, li { white-space: pre-wrap; }
<y>273</y>
</hint>
<hint type="destinationlabel" >
<x>261</x>
<y>275</y>
<x>322</x>
<y>285</y>
</hint>
</hints>
</connection>
@ -1890,8 +1911,8 @@ p, li { white-space: pre-wrap; }
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel" >
<x>265</x>
<y>270</y>
<x>322</x>
<y>285</y>
</hint>
<hint type="destinationlabel" >
<x>247</x>
@ -1926,8 +1947,8 @@ p, li { white-space: pre-wrap; }
<y>51</y>
</hint>
<hint type="destinationlabel" >
<x>47</x>
<y>56</y>
<x>67</x>
<y>257</y>
</hint>
</hints>
</connection>
@ -1974,8 +1995,8 @@ p, li { white-space: pre-wrap; }
<y>47</y>
</hint>
<hint type="destinationlabel" >
<x>49</x>
<y>183</y>
<x>60</x>
<y>217</y>
</hint>
</hints>
</connection>
@ -2070,8 +2091,8 @@ p, li { white-space: pre-wrap; }
<y>56</y>
</hint>
<hint type="destinationlabel" >
<x>130</x>
<y>180</y>
<x>141</x>
<y>214</y>
</hint>
</hints>
</connection>
@ -2098,12 +2119,12 @@ p, li { white-space: pre-wrap; }
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>55</x>
<y>50</y>
<x>87</x>
<y>72</y>
</hint>
<hint type="destinationlabel" >
<x>96</x>
<y>56</y>
<x>116</x>
<y>194</y>
</hint>
</hints>
</connection>
@ -2130,8 +2151,8 @@ p, li { white-space: pre-wrap; }
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>131</x>
<y>42</y>
<x>161</x>
<y>72</y>
</hint>
<hint type="destinationlabel" >
<x>165</x>
@ -2166,8 +2187,8 @@ p, li { white-space: pre-wrap; }
<y>49</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>123</x>
<y>438</y>
</hint>
</hints>
</connection>
@ -2178,12 +2199,12 @@ p, li { white-space: pre-wrap; }
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>161</x>
<y>72</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>123</x>
<y>438</y>
</hint>
</hints>
</connection>
@ -2198,8 +2219,8 @@ p, li { white-space: pre-wrap; }
<y>53</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>123</x>
<y>257</y>
</hint>
</hints>
</connection>
@ -2214,8 +2235,8 @@ p, li { white-space: pre-wrap; }
<y>56</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>123</x>
<y>525</y>
</hint>
</hints>
</connection>
@ -2226,12 +2247,12 @@ p, li { white-space: pre-wrap; }
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>161</x>
<y>72</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>123</x>
<y>525</y>
</hint>
</hints>
</connection>
@ -2242,12 +2263,12 @@ p, li { white-space: pre-wrap; }
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>103</x>
<y>56</y>
<x>161</x>
<y>72</y>
</hint>
<hint type="destinationlabel" >
<x>103</x>
<y>56</y>
<x>123</x>
<y>257</y>
</hint>
</hints>
</connection>
@ -2262,8 +2283,8 @@ p, li { white-space: pre-wrap; }
<y>56</y>
</hint>
<hint type="destinationlabel" >
<x>42</x>
<y>56</y>
<x>62</x>
<y>525</y>
</hint>
</hints>
</connection>
@ -2278,8 +2299,8 @@ p, li { white-space: pre-wrap; }
<y>56</y>
</hint>
<hint type="destinationlabel" >
<x>72</x>
<y>56</y>
<x>92</x>
<y>257</y>
</hint>
</hints>
</connection>