/**************************************************************************** * MeshLab o o * * A versatile mesh processing toolbox o o * * _ O _ * * Copyright(C) 2005 \/)\/ * * Visual Computing Lab /\/| * * ISTI - Italian National Research Council | * * \ * * All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * * for more details. * * * ****************************************************************************/ #include "edit_paint.h" #include #include using namespace std; using namespace vcg; EditPaintPlugin::EditPaintPlugin() { zbuffer = NULL; color_buffer = NULL; clone_zbuffer = NULL; generateCircle(circle); generateCircle(dense_circle, 64); generateSquare(square); generateSquare(dense_square, 16); } EditPaintPlugin::~EditPaintPlugin() {} const QString EditPaintPlugin::info() { return tr("Improved Painting"); } void EditPaintPlugin::suggestedRenderingData(MeshModel & m, MLRenderingData& dt) { if (m.cm.VN() == 0) return; MLRenderingData::PRIMITIVE_MODALITY pr = MLRenderingData::PR_POINTS; if (m.cm.FN() > 0) pr = MLRenderingData::PR_SOLID; MLRenderingData::RendAtts atts; atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true; atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true; atts[MLRenderingData::ATT_NAMES::ATT_VERTCOLOR] = true; MLPerViewGLOptions opts; dt.get(opts); opts._sel_enabled = true; opts._face_sel = true; opts._vertex_sel = true; dt.set(opts); dt.set(pr, atts); } bool EditPaintPlugin::startEdit(MeshModel & m, GLArea * parent, MLSceneGLSharedDataContext* /*cont*/) { if (!GLExtensionsManager::initializeGLextensions_notThrowing()) return false; dock = new QDockWidget(parent->window()); paintbox = new Paintbox(dock); dock->setAllowedAreas(Qt::NoDockWidgetArea); dock->setWidget(paintbox); QPoint p = parent->mapToGlobal(QPoint(0, 0)); dock->setGeometry(5 + p.x(), p.y() + 5, paintbox->width(), parent->height() - 10); dock->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum); dock->setFloating(true); dock->setVisible(true); tri::UpdateBounding::Box(m.cm); m.updateDataMask(MeshModel::MM_VERTFACETOPO | MeshModel::MM_FACEMARK | MeshModel::MM_VERTMARK); if (!m.hasDataMask(MeshModel::MM_VERTCOLOR)) { m.updateDataMask(MeshModel::MM_VERTCOLOR); tri::UpdateColor::PerVertexConstant(m.cm, Color4b(150, 150, 150, 255)); } tri::InitFaceIMark(m.cm); tri::InitVertexIMark(m.cm); QObject::connect(paintbox, SIGNAL(undo()), this, SLOT(update())); QObject::connect(paintbox, SIGNAL(redo()), this, SLOT(update())); QObject::connect(paintbox, SIGNAL(typeChange(ToolType)), this, SLOT(setToolType(ToolType))); parent->update(); selection = new vector(); latest_event.pressure = 0.0; setToolType(COLOR_PAINT); glarea = parent; buffer_width = glarea->width(); buffer_height = glarea->height(); glarea->setMouseTracking(true); //connect(this, SIGNAL(setSelectionRendering(bool)), glarea, SLOT(setSelectFaceRendering(bool))); parent->setCursor(QCursor(QPixmap(":/images/cursor_paint.png"), 1, 1)); // initialize once and for all the radius (will remain the same all the time) current_brush.radius = (paintbox->getRadius() * m.cm.bbox.Diag() * 0.5); if (glarea->mvc() == NULL) return false; MLSceneGLSharedDataContext* shared = glarea->mvc()->sharedDataContext(); updateColorBuffer(m, shared); shared->manageBuffers(m.id()); return true; } void EditPaintPlugin::endEdit(MeshModel &/* m*/, GLArea * /*parent*/, MLSceneGLSharedDataContext* /*cont*/) { QObject::disconnect(paintbox, SIGNAL(undo()), this, SLOT(update())); QObject::disconnect(paintbox, SIGNAL(redo()), this, SLOT(update())); glarea->setMouseTracking(false); if (zbuffer != NULL) { delete zbuffer; zbuffer = NULL; } delete paintbox; delete selection; delete dock; } void EditPaintPlugin::mousePressEvent(QMouseEvent * event, MeshModel &, GLArea * gla) { // start a new stroke: init zbuffer and update brush if (zbuffer != NULL) { delete zbuffer; zbuffer = NULL; } current_brush.size = paintbox->getSize(); current_brush.opacity = paintbox->getOpacity(); current_brush.hardness = paintbox->getHardness(); pushInputEvent(event->type(), event->pos(), event->modifiers(), 1, event->button(), gla); gla->update(); } void EditPaintPlugin::mouseMoveEvent(QMouseEvent* event, MeshModel & /*m*/, GLArea * gla) { if (gla == NULL) return; pushInputEvent(event->type(), event->pos(), event->modifiers(), latest_event.pressure, latest_event.button, gla); gla->update(); } void EditPaintPlugin::mouseReleaseEvent(QMouseEvent * event, MeshModel &, GLArea * gla) { pushInputEvent(event->type(), event->pos(), event->modifiers(), 0, event->button(), gla); gla->updateAllSiblingsGLAreas(); } void EditPaintPlugin::tabletEvent(QTabletEvent * event, MeshModel &, GLArea * gla) { if (!(paintbox->getPressureFrameEnabled())) paintbox->enablePressureFrame(); event->accept(); // if event is down, start a new stroke: clean zbuff if (event->type() == QEvent::TabletPress) { if (zbuffer != NULL) { delete zbuffer; zbuffer = NULL; } current_brush.size = paintbox->getSize(); current_brush.opacity = paintbox->getOpacity(); current_brush.hardness = paintbox->getHardness(); } pushInputEvent(event->type(), event->pos(), event->modifiers(), event->pressure(), (event->pointerType() == QTabletEvent::Eraser) ? Qt::RightButton : Qt::LeftButton, gla); gla->update(); } void EditPaintPlugin::setToolType(ToolType t) { current_type = t; switch (current_type) { case MESH_SELECT: current_options = EPP_PICK_FACES | EPP_DRAW_CURSOR; emit setSelectionRendering(true); break; case COLOR_PAINT: case COLOR_NOISE: case COLOR_CLONE: case COLOR_SMOOTH: case MESH_SMOOTH: current_options = EPP_PICK_VERTICES | EPP_DRAW_CURSOR; break; case MESH_PULL: case MESH_PUSH: current_options = EPP_PICK_VERTICES | EPP_AVG_NORMAL | EPP_DRAW_CURSOR; break; case COLOR_FILL: case COLOR_GRADIENT: case COLOR_PICK: default: current_options = EPP_NONE; } } void EditPaintPlugin::setBrushSettings(int size, int opacity, int hardness) { current_brush.size = size; current_brush.opacity = opacity; current_brush.hardness = hardness; } /** * 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. * */ void EditPaintPlugin::decorate(MeshModel &m, GLArea * gla) { glarea = gla; if (gla->mvc() == NULL) return; MLSceneGLSharedDataContext* shared = gla->mvc()->sharedDataContext(); if (shared == NULL) return; if (!latest_event.valid || latest_event.processed) return; latest_event.processed = true; glPushAttrib(GL_TRANSFORM_BIT); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glMultMatrix(m.cm.Tr); glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix); glPopMatrix(); glPopAttrib(); glGetDoublev(GL_PROJECTION_MATRIX, projection_matrix); viewport[0] = viewport[1] = 0; viewport[2] = gla->width(); viewport[3] = gla->height(); if (zbuffer == NULL) { zbuffer = new GLfloat[gla->width()*gla->height()]; glReadPixels(0, 0, gla->width(), gla->height(), GL_DEPTH_COMPONENT, GL_FLOAT, zbuffer); } if (current_options & EPP_DRAW_CURSOR) { current_brush.radius = (paintbox->getRadius() * m.cm.bbox.Diag() * 0.5); if ((paintbox->getPressureFrameEnabled())) { if (paintbox->getPressureSize()) current_brush.size = paintbox->getSize() * latest_event.pressure; if (paintbox->getPressureOpacity()) current_brush.opacity = paintbox->getOpacity() * latest_event.pressure; if (paintbox->getPressureHardness()) current_brush.hardness = paintbox->getHardness() * latest_event.pressure; } if (paintbox->getSizeUnit() == 0) drawSimplePolyLine(gla, latest_event.position, paintbox->getSize(), (paintbox->getBrush() == CIRCLE) ? &circle : &square); else drawPercentualPolyLine(glarea, latest_event.gl_position, m, zbuffer, modelview_matrix, projection_matrix, viewport, current_brush.radius, (paintbox->getBrush() == CIRCLE) ? &dense_circle : &dense_square); } if (latest_event.pressure > 0) { if (current_options & EPP_PICK_VERTICES) { vertices.clear(); updateSelection(m, &vertices); } else if (current_options & EPP_PICK_FACES) updateSelection(m); if (previous_event.pressure == 0) { /************************* * ON BRUSH DOWN * *************************/ paintbox->setUndoStack(glarea); switch (current_type) { case COLOR_PAINT: { painted_vertices.clear(); QColor newcol; if (latest_event.button == Qt::LeftButton) newcol = paintbox->getForegroundColor(); else newcol = paintbox->getBackgroundColor(); color[0] = newcol.red(); color[1] = newcol.green(); color[2] = newcol.blue(); color[3] = newcol.alpha(); paintbox->getUndoStack()->beginMacro("Color Paint"); paint(&vertices); } break; case COLOR_CLONE: //Clone Source Acquisition if (latest_event.modifiers & Qt::ControlModifier || latest_event.button == Qt::RightButton) { if (color_buffer != NULL) delete color_buffer; if (clone_zbuffer != NULL) delete clone_zbuffer; color_buffer = NULL, clone_zbuffer = NULL; /*QMap::iterator it = glarea->rendermodemap.find(m.id()); if (it == glarea->rendermodemap.end()) return; it.value().lighting = false;*/ current_options &= ~EPP_DRAW_CURSOR; glarea->update(); } //Clone Painting else { //There's a new image to get if (paintbox->isNewPixmapAvailable()) { paintbox->getPixmapBuffer(color_buffer, clone_zbuffer, buffer_width, buffer_height); // source_delta.setX(buffer_width/2); // source_delta.setY(buffer_height/2); source_delta = paintbox->getPixmapDelta(); paintbox->setPixmapOffset(0, 0); apply_start = latest_event.position; painted_vertices.clear(); paintbox->getUndoStack()->beginMacro("Color Clone"); paint(&vertices); } else if (color_buffer != NULL) //There's still something in the buffer { painted_vertices.clear(); source_delta = paintbox->getPixmapDelta(); paintbox->setPixmapOffset(0, 0); apply_start = latest_event.position; paintbox->getUndoStack()->beginMacro("Color Clone"); paint(&vertices); } } break; case COLOR_NOISE: painted_vertices.clear(); noise_scale = paintbox->getNoiseSize() * 10 / m.cm.bbox.Diag(); paintbox->getUndoStack()->beginMacro("Color Noise"); break; case COLOR_GRADIENT: gradient_start = latest_event.position; break; case MESH_PULL: case MESH_PUSH: displaced_vertices.clear(); paintbox->getUndoStack()->beginMacro("Mesh Sculpting"); sculpt(m, &vertices); break; case COLOR_SMOOTH: paintbox->getUndoStack()->beginMacro("Color Smooth"); smoothed_vertices.clear(); tri::UnMarkAll(m.cm); break; case MESH_SMOOTH: paintbox->getUndoStack()->beginMacro("Mesh Smooth"); smoothed_vertices.clear(); tri::UnMarkAll(m.cm); break; default: break; } } else { /************************* * ON BRUSH MOVE * *************************/ switch (current_type) { case COLOR_CLONE: { paintbox->setPixmapOffset(latest_event.position.x() - apply_start.x(), latest_event.position.y() - apply_start.y()); if (color_buffer != NULL) { paint(&vertices); updateColorBuffer(m, shared); } } break; case COLOR_PAINT: case COLOR_NOISE: { paint(&vertices); updateColorBuffer(m, shared); } break; case COLOR_GRADIENT: { drawLine(glarea, gradient_start, latest_event.position); updateColorBuffer(m, shared); } break; case MESH_SELECT: { for (vector::iterator fpi = selection->begin(); fpi != selection->end(); ++fpi) { if (latest_event.button == Qt::LeftButton)(*fpi)->SetS(); else (*fpi)->ClearS(); } glarea->updateSelection(m.id(), false, true); } break; case MESH_PUSH: case MESH_PULL: { sculpt(m, &vertices); updateGeometryBuffers(m, shared); } break; case COLOR_SMOOTH: { smooth(&vertices); updateColorBuffer(m, shared); } break; case MESH_SMOOTH: { smooth(&vertices); updateGeometryBuffers(m, shared); } break; default: break; } } } else { if (previous_event.pressure > 0) { /************************* * ON BRUSH UP * *************************/ switch (current_type) { case COLOR_FILL: { //CFaceO * face; std::vector res; int nface = GLPickTri::PickVisibleFace(latest_event.gl_position.x(), latest_event.gl_position.y(), m.cm, res, 2, 2); //if (GLPickTri::PickClosestFace(latest_event.gl_position.x(), latest_event.gl_position.y(), m.cm, face, 2, 2)) if ((nface > 0) && (res[0] != NULL) && !(res[0]->IsD())) { fill(m, res[0]); updateColorBuffer(m, shared); } } break; case COLOR_PICK: { QColor color; CVertexO * temp_vert = NULL; if (paintbox->getPickMode() == 0) { if (getVertexAtMouse(m, temp_vert, latest_event.gl_position, modelview_matrix, projection_matrix, viewport)) { color.setRgb(temp_vert->C()[0], temp_vert->C()[1], temp_vert->C()[2], 255); (latest_event.button == Qt::LeftButton) ? paintbox->setForegroundColor(color) : paintbox->setBackgroundColor(color); } } else { GLubyte pixel[3]; glReadPixels(latest_event.gl_position.x(), latest_event.gl_position.y(), 1, 1, GL_RGB, GL_UNSIGNED_BYTE, (void *)pixel); color.setRgb(pixel[0], pixel[1], pixel[2], 255); (latest_event.button == Qt::LeftButton) ? paintbox->setForegroundColor(color) : paintbox->setBackgroundColor(color); } paintbox->restorePreviousType(); } break; case COLOR_GRADIENT: gradient(m, gla); updateColorBuffer(m, shared); break; case COLOR_CLONE: if (latest_event.modifiers & Qt::ControlModifier || latest_event.button == Qt::RightButton) { capture(); break; } else paintbox->movePixmapDelta(-latest_event.position.x() + apply_start.x(), -latest_event.position.y() + apply_start.y()); case COLOR_SMOOTH: case COLOR_NOISE: case COLOR_PAINT: case MESH_SMOOTH: case MESH_PUSH: case MESH_PULL: paintbox->getUndoStack()->endMacro(); default: break; } } } shared->manageBuffers(m.id()); } inline void EditPaintPlugin::smooth(vector< pair > * vertices) { QHash > originals; Color4b newcol, destCol; int strength = paintbox->getSmoothPercentual(); int decrease_pos = paintbox->getHardness(); int c_r, c_g, c_b; float p_x, p_y, p_z; float newpos[3]; if (paintbox->getPressureDisplacement()) strength *= latest_event.pressure; for (unsigned int k = 0; k < vertices->size(); k++) //forach selected vertices { pair data = vertices->at(k); CVertexO * v = data.first; PickingData * vd = &data.second; pair 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 ((!smoothed_vertices.contains(v)) && (glarea != NULL) && (glarea->mvc() != NULL) && (glarea->md() != NULL) && (glarea->md()->mm() != NULL)) { if (paintbox->getCurrentType() == COLOR_SMOOTH) paintbox->getUndoStack()->push(new SingleColorUndo(v, v->C())); else paintbox->getUndoStack()->push(new SinglePositionUndo(v, v->P(), v->N())); smoothed_vertices.insert(v, v); } 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 vertices pos or and vertices 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 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*strength) / 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*strength) / 100.0, newpos, po, newpos); for (int lauf = 0; lauf < 3; lauf++) v->P()[lauf] = newpos[lauf]; } } if (paintbox->getCurrentType() == MESH_SMOOTH) { updateNormal(v); } } } inline void EditPaintPlugin::sculpt(MeshModel & /*m*/, vector< pair > * vertices) { // int opac = 1.0; float decrease_pos = paintbox->getHardness() / 100.0; float strength = paintbox->getDisplacement() / 100.0; if (paintbox->getPressureDisplacement()) strength *= latest_event.pressure; if (latest_event.button == Qt::RightButton) strength = -strength; if (normal[0] == normal[1] && normal[1] == normal[2] && normal[2] == 0) { return; } for (unsigned int k = 0; k < vertices->size(); k++) { pair data = vertices->at(k); float op = brush(paintbox->getBrush(), data.second.distance, data.second.rel_position.x(), data.second.rel_position.y(), decrease_pos * 100); //TODO Precalculate this monster! float gauss = (strength * exp(-(op - 1.0)*(op - 1.0) * 8.0)); bool useAverageNormals = paintbox->getDirection() == 0; //0 is average normal, 1 is per-vertex normal if (!displaced_vertices.contains(data.first)) { displaced_vertices.insert(data.first, pair( Point3f(data.first->P()[0], data.first->P()[1], data.first->P()[2]), gauss)); paintbox->getUndoStack()->push(new SinglePositionUndo(data.first, data.first->P(), data.first->N())); if (useAverageNormals) displaceAlongVector(data.first, normal, gauss); else displaceAlongVector(data.first, data.first->N(), gauss); updateNormal(data.first); } else if ((latest_event.button == Qt::RightButton) ? displaced_vertices[data.first].second > gauss : displaced_vertices[data.first].second < gauss) { displaced_vertices[data.first].second = gauss; 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]; if (useAverageNormals) displaceAlongVector(data.first, normal, gauss); else displaceAlongVector(data.first, data.first->N(), gauss); updateNormal(data.first); } delete zbuffer; zbuffer = NULL; } } inline void EditPaintPlugin::capture() { color_buffer = new GLubyte[glarea->width()*glarea->height() * 4]; clone_zbuffer = new GLfloat[glarea->width()*glarea->height()]; glReadPixels(0, 0, glarea->width(), glarea->height(), GL_RGBA, GL_UNSIGNED_BYTE, color_buffer); glReadPixels(0, 0, glarea->width(), glarea->height(), GL_DEPTH_COMPONENT, GL_FLOAT, clone_zbuffer); buffer_height = glarea->height(); buffer_width = glarea->width(); source_delta = latest_event.position; QImage image(glarea->width(), glarea->height(), QImage::Format_ARGB32); for (int x = 0; x < glarea->width(); x++) { for (int y = 0; y < glarea->height(); y++) { int index = (y * glarea->width() + x) * 4; image.setPixel(x, glarea->height() - y - 1, qRgba((int)color_buffer[index], (int)color_buffer[index + 1], (int)color_buffer[index + 2], (int)color_buffer[index + 3])); } } //if (glarea->getCurrentRenderMode() == NULL) // return; //glarea->getCurrentRenderMode()->lighting = true; current_options |= EPP_DRAW_CURSOR; paintbox->setClonePixmap(image); paintbox->setPixmapDelta(source_delta.x(), source_delta.y()); // paintbox->setPixmapCenter(-source_delta.x(), -source_delta.y()); glarea->update(); } inline bool EditPaintPlugin::accessCloneBuffer(int vertex_x, int vertex_y, vcg::Color4b & color) { int y = buffer_height - source_delta.y() + (vertex_y + apply_start.y() - glarea->height()); int x = source_delta.x() + (vertex_x - apply_start.x()); int index = y * buffer_width + x; if (x < buffer_width && y < buffer_height && x >= 0 && y >= 0) { if (clone_zbuffer[index] < 1.0) { index *= 4; color[0] = color_buffer[index]; color[1] = color_buffer[index + 1]; color[2] = color_buffer[index + 2], color[3] = color_buffer[index + 3]; return true; } } return false; } /** * Painting, Cloning and Noise! */ inline void EditPaintPlugin::paint(vector< pair > * vertices) { int opac = current_brush.opacity; //TODO legacy assignment int decrease_pos = current_brush.hardness; //TODO legacy assignment for (unsigned int k = 0; k < vertices->size(); k++) { pair 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 ((glarea != NULL) && (glarea->mvc() != NULL) && (glarea->md() != NULL) && (glarea->md()->mm() != NULL)) { if (!painted_vertices.contains(data.first)) { if (paintbox->getCurrentType() == COLOR_CLONE) if (!accessCloneBuffer(data.second.position.x(), data.second.position.y(), color)) return; if (paintbox->getCurrentType() == COLOR_NOISE) computeNoiseColor(data.first, color); painted_vertices.insert(data.first, pair( Color4b(data.first->C()[0], data.first->C()[1], data.first->C()[2], data.first->C()[3]), (int)(op*opac))); paintbox->getUndoStack()->push(new SingleColorUndo(data.first, data.first->C())); applyColor(data.first, color, (int)(op * opac)); } else if (painted_vertices[data.first].second < (int)(op * opac)) { painted_vertices[data.first].second = (int)(op * opac); Color4b temp = painted_vertices[data.first].first; data.first->C()[0] = temp[0]; data.first->C()[1] = temp[1]; data.first->C()[2] = temp[2]; if (paintbox->getCurrentType() == COLOR_CLONE) if (!accessCloneBuffer(data.second.position.x(), data.second.position.y(), color)) return; if (paintbox->getCurrentType() == COLOR_NOISE) computeNoiseColor(data.first, color); paintbox->getUndoStack()->push(new SingleColorUndo(data.first, data.first->C())); applyColor(data.first, color, (int)(op * opac)); } } } } inline void EditPaintPlugin::computeNoiseColor(CVertexO * vert, vcg::Color4b & col) { float scaler = noise_scale; //parameter TODO to be cached double noise; //if "veins on" // noise = vcg::math::Abs(vcg::math::Perlin::Noise(vert->P()[0] * scaler, vert->P()[1] * scaler, vert->P()[2] * scaler)); //else noise = (vcg::math::Perlin::Noise(vert->P()[0] * scaler, vert->P()[1] * scaler, vert->P()[2] * scaler) + 1) / 2; Color4b forecolor(paintbox->getForegroundColor().red(), paintbox->getForegroundColor().green(), paintbox->getForegroundColor().blue(), paintbox->getForegroundColor().alpha()); //TODO test code to be refactored if (paintbox->getGradientType() == 0) { Color4b backcolor(paintbox->getBackgroundColor().red(), paintbox->getBackgroundColor().green(), paintbox->getBackgroundColor().blue(), paintbox->getBackgroundColor().alpha()); for (int i = 0; i < 4; i++) col[i] = forecolor[i] * noise + backcolor[i] * (1.0 - noise); } else { for (int i = 0; i < 4; i++) col[i] = forecolor[i] * noise + vert->C()[i] * (1.0 - noise); } } /** * fills the mesh starting from face. * If face is selected, it will fill only the selected area, * otherwise only the non selected area */ inline void EditPaintPlugin::fill(MeshModel &, CFaceO * face) { QHash visited; QHash temp; vector temp_po; bool who = face->IsS(); temp_po.push_back(face); visited.insert(face, face); int opac = paintbox->getOpacity(); QColor newcol = (latest_event.button == Qt::LeftButton) ? paintbox->getForegroundColor() : paintbox->getBackgroundColor(); Color4b color(newcol.red(), newcol.green(), newcol.blue(), newcol.alpha()); paintbox->getUndoStack()->beginMacro("Fill Color"); // MLSceneGLSharedDataContext* shared = NULL; // int meshid; // if ((glarea != NULL) && (glarea->mvc() != NULL) && (glarea->md() != NULL) && (glarea->md()->mm() != NULL)) // { // shared = glarea->mvc()->sharedDataContext(); // meshid = glarea->md()->mm()->id(); // } for (unsigned int lauf2 = 0; lauf2 < temp_po.size(); lauf2++) { CFaceO * fac = temp_po.at(lauf2); if (who == fac->IsS()) { for (int lauf = 0; lauf < 3; lauf++) { if (!temp.contains(fac->V(lauf))) { temp.insert(fac->V(lauf), fac->V(lauf)); paintbox->getUndoStack()->push(new SingleColorUndo(fac->V(lauf), fac->V(lauf)->C())); applyColor(fac->V(lauf), color, opac); } } vector surround; for (int lauf = 0; lauf < 3; lauf++) getSurroundingFacesVF(fac, lauf, &surround); for (unsigned int lauf3 = 0; lauf3 < surround.size(); lauf3++) { if (!visited.contains(surround[lauf3])) { temp_po.push_back(surround[lauf3]); visited.insert(surround[lauf3], surround[lauf3]); } } } } paintbox->getUndoStack()->endMacro(); } inline void EditPaintPlugin::gradient(MeshModel & m, GLArea * gla) { QPoint p = gradient_start - latest_event.position; QHash temp; int opac = paintbox->getOpacity(); QColor qc1 = paintbox->getForegroundColor(); QColor qc2 = paintbox->getBackgroundColor(); Color4b c1(qc1.red(), qc1.green(), qc1.blue(), qc1.alpha()); Color4b c2(qc2.red(), qc2.green(), qc2.blue(), qc2.alpha()); QPointF p1(gradient_start.x(), gla->height() - gradient_start.y()); QPointF p0(latest_event.gl_position); float x2 = (p1.x() - p0.x()); float y2 = (p1.y() - p0.y()); float l_square = x2*x2 + y2*y2; CVertexO * vertex; CMeshO::FaceIterator fi; double dx, dy, dz; Color4b merger; bool tutti = !hasSelected(m); float radius = sqrt((float)(p.x()*p.x() + p.y()*p.y())); paintbox->getUndoStack()->beginMacro("Gradient"); // MLSceneGLSharedDataContext* shared = NULL; // int meshid; // if ((glarea != NULL) && (glarea->mvc() != NULL) && (glarea->md() != NULL) && (glarea->md()->mm() != NULL)) // { // shared = glarea->mvc()->sharedDataContext(); // meshid = glarea->md()->mm()->id(); // } int gradient_type = paintbox->getGradientType(); int gradient_form = paintbox->getGradientForm(); for (fi = m.cm.face.begin(); fi != m.cm.face.end(); ++fi) { if (!(*fi).IsD() && (tutti || (*fi).IsS())){ for (int lauf = 0; lauf < 3; lauf++) { if (!temp.contains((*fi).V(lauf))) { vertex = (*fi).V(lauf); temp.insert(vertex, vertex); gluProject(vertex->P()[0], vertex->P()[1], vertex->P()[2], modelview_matrix, projection_matrix, viewport, &dx, &dy, &dz); paintbox->getUndoStack()->push(new SingleColorUndo(vertex, vertex->C())); if (gradient_form == 0) { double r = (dx - p0.x())*(p1.x() - p0.x()) + (dy - p0.y())*(p1.y() - p0.y()); r = r / l_square; float px = p0.x() + r*(p1.x() - p0.x()); float py = p0.y() + r*(p1.y() - p0.y()); px = px - dx; py = py - dy; if (gradient_type == 0) { if (r >= 0 && r <= 1) { mergeColors(r, c1, c2, &merger); applyColor(vertex, merger, opac); } else if (r > 1) { applyColor(vertex, c1, opac); } else if (r < 0) { applyColor(vertex, c2, opac); } } else { if (r >= 0 && r <= 1) { applyColor(vertex, c1, (int)((opac * 0.01) * r * 100.0)); } else if (r > 1) { applyColor(vertex, c1, opac); } } } else { float x0 = (dx - p1.x()); float y0 = (dy - p1.y()); float bla0 = x0*x0 + y0*y0; if (bla0 < radius*radius && radius>0) { float r = 1.0 - sqrt(bla0) / sqrt(radius*radius); if (gradient_type == 0) { mergeColors(r, c1, c2, &merger); applyColor(vertex, merger, opac); } else { applyColor(vertex, c1, (int)((opac * 0.01) * r * 100.0)); } } } } } } } 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! */ inline void EditPaintPlugin::updateSelection(MeshModel &m, vector< pair > * vertex_result) { vector::iterator fpi; vector temp; //TODO maybe temp can be placed inside the class for better performance vector surround; /*< surrounding faces of a given face*/ surround.reserve(6); if (current_options & EPP_AVG_NORMAL) normal = Point3m(0.0, 0.0, 0.0); tri::UnMarkAll(m.cm); 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)); } } } 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); } } selection->clear(); QPointF gl_cursorf = QPointF(latest_event.gl_position); QPointF gl_prev_cursorf = QPointF(previous_event.gl_position); QPointF p[3], z[3]; //p stores vertex coordinates in screen space, z the corresponding depth value double tx, ty, tz; bool backface = paintbox->getPaintBackFace(); bool invisible = paintbox->getPaintInvisible(); bool percentual = paintbox->getSizeUnit() == 1; double EPSILON = 0.003; double scale_fac = 1.0; float fov = 1.0; double distance[3]; if (percentual) { double dX, dY, dZ; fastMultiply(0, 0, 0, modelview_matrix, &dX, &dY, &dZ); fastMultiply(0, 1, 0, modelview_matrix, &tx, &ty, &tz); scale_fac = sqrt((ty - dY)*(ty - dY) + (tx - dX)*(tx - dX) + (tz - dZ)*(tz - dZ)); /** to get the correct radius depending on distance but on fov too */ float fo = glarea->getFov()*0.5; fov = 1.0 / tan(fo*M_PI / 180.0)*0.5; } for (unsigned int k = 0; k < temp.size(); k++) //for each face { CFaceO * fac = temp.at(k); bool intern = false; 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 { if (gluProject((fac)->V(i)->P()[0], (fac)->V(i)->P()[1], (fac)->V(i)->P()[2], modelview_matrix, projection_matrix, viewport, &tx, &ty, &tz) == GL_TRUE) checkable++; //if gluProjection works if (tz < 0 || tz > 1) checkable--; //but if outside depth bounds p[i].setX(tx); p[i].setY(ty); //store the position of the vertex on the screen //if the vertex is projected within the screen (i.e. it's visible) if (tx >= 0 && tx < viewport[2] && ty >= 0 && ty < viewport[3]) { z[i].setX(tz); //the screen depth of the point z[i].setY((float)zbuffer[(int)(((int)ty)*viewport[2] + (int)tx)]); //the screen depth of the closest point at the same coors } else { z[i].setX(1); //far z[i].setY(0); //near } if (percentual) { double dx, dy, dz; // distance fastMultiply((fac)->V(i)->P()[0], (fac)->V(i)->P()[1], (fac)->V(i)->P()[2], modelview_matrix, &dx, &dy, &dz); distance[i] = dz; } } if (backface || isFront(p[0], p[1], p[2])) { for (int j = 0; j < 3; j++) if (invisible || (z[j].x() <= z[j].y() + EPSILON)) { PickingData vd; //TODO make it a pointer float radius = percentual ? vcg::math::Abs(current_brush.radius * scale_fac * viewport[3] * fov / distance[j]) : current_brush.size; 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 (!tri::IsMarked(m.cm, fac->V(j))) { vd.position.setX((int)p[j].x()); vd.position.setY((int)p[j].y()); pair data(fac->V(j), vd); vertex_result->push_back(data); if (current_options & EPP_AVG_NORMAL) normal += fac->V(j)->N(); tri::Mark(m.cm, fac->V(j)); } } if (vertex_result == NULL && !intern && lineHitsCircle(p[j], p[(j + 1) % 3], gl_cursorf, radius)) { intern = true; continue; } } if (checkable == 3 && !intern && (pointInTriangle(gl_cursorf, p[0], p[1], p[2]) || pointInTriangle(gl_prev_cursorf, p[0], p[1], p[2]))) { intern = true; } } if (intern && !tri::IsMarked(m.cm, fac)) { tri::Mark(m.cm, fac); selection->push_back(fac); surround.clear(); for (int lauf = 0; lauf < 3; lauf++) getSurroundingFacesVF(fac, lauf, &surround); for (unsigned int lauf3 = 0; lauf3 < surround.size(); lauf3++) { if (!tri::IsMarked(m.cm, surround[lauf3])) { temp.push_back(surround[lauf3]); } } } } //end of for each face loop if (current_options & EPP_AVG_NORMAL) normal /= vertex_result->size(); } void EditPaintPlugin::updateColorBuffer(MeshModel& m, MLSceneGLSharedDataContext* shared) { if (shared != NULL) { MLRenderingData::RendAtts atts; atts[MLRenderingData::ATT_NAMES::ATT_VERTCOLOR] = true; shared->meshAttributesUpdated(m.id(), false, atts); } } void EditPaintPlugin::updateGeometryBuffers(MeshModel & m, MLSceneGLSharedDataContext* shared) { if (shared != NULL) { MLRenderingData::RendAtts atts; atts[MLRenderingData::ATT_NAMES::ATT_VERTPOSITION] = true; atts[MLRenderingData::ATT_NAMES::ATT_VERTNORMAL] = true; atts[MLRenderingData::ATT_NAMES::ATT_FACENORMAL] = true; shared->meshAttributesUpdated(m.id(), false, atts); } } /** * Request an async repainting of the glarea * * This slot is connected to undo and redo */ void EditPaintPlugin::update() { if ((glarea != NULL) && (glarea->mvc() != NULL) && (glarea->md() != NULL) && (glarea->md()->mm() != NULL)) { updateColorBuffer(*(glarea->md()->mm()),glarea->mvc()->sharedDataContext()); updateGeometryBuffers(*(glarea->md()->mm()), glarea->mvc()->sharedDataContext()); glarea->mvc()->sharedDataContext()->manageBuffers(glarea->md()->mm()->id()); } glarea->updateAllSiblingsGLAreas(); } /*********OpenGL Drawing Routines************/ /** * draws the xor-line */ void drawLine(GLArea * gla, QPoint & start, QPoint & cur) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0, gla->width(), gla->height(), 0, -1, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glPushAttrib(GL_ENABLE_BIT); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glEnable(GL_COLOR_LOGIC_OP); float wi; glGetFloatv(GL_LINE_WIDTH, &wi); glLineWidth(4); glLogicOp(GL_XOR); glColor3f(1, 1, 1); glBegin(GL_LINES); glVertex2f(start.x(), start.y()); glVertex2f(cur.x(), cur.y()); glEnd(); glPopAttrib(); glPopMatrix(); // restore modelview glLineWidth(wi); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } void drawSimplePolyLine(GLArea * gla, QPoint & cur, float scale, vector * points) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0, gla->width(), gla->height(), 0, -1, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glPushAttrib(GL_ENABLE_BIT); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_XOR); glColor3f(1, 1, 1); glBegin(GL_LINE_LOOP); for (unsigned int k = 0; k < points->size(); k++) { glVertex2f(cur.x() + (points->at(k).x() * scale), cur.y() + (points->at(k).y() * scale)); } glEnd(); glDisable(GL_LOGIC_OP); glPopAttrib(); glPopMatrix(); // restore modelview glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } void drawPercentualPolyLine(GLArea * gla, QPoint &mid, MeshModel &m, GLfloat* pixels, double* modelview_matrix, double* projection_matrix, GLint* viewport, float scale, vector * points) { double dX, dY, dZ; //near double dX2, dY2, dZ2; //far gluUnProject((double)mid.x(), mid.y(), 0.0, modelview_matrix, projection_matrix, viewport, &dX, &dY, &dZ); gluUnProject((double)mid.x(), mid.y(), 1.0, modelview_matrix, projection_matrix, viewport, &dX2, &dY2, &dZ2); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); gluLookAt(dX, dY, dZ, dX2, dY2, dZ2, 1, 0, 0); double mvmatrix2[16]; glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix2); glPopMatrix(); double tx, ty, tz; double tx2, ty2, tz2; double inv_mvmatrix[16]; Matrix44d temp(mvmatrix2); temp = Inverse(temp); for (int k = 0; k < 16; k++) inv_mvmatrix[k] = temp[k / 4][k % 4]; double a, b, c; double a2, b2, c2; int STEPS = 30; int DSCALER = -7; float diag = m.cm.bbox.Diag() * DSCALER; float radius = scale; //TODO leftover QVector proj_points(int(points->size())); for (size_t i = 0; i < points->size(); i++) { /** calcs the far point of the line */ fastMultiply(points->at(i).x() * radius, points->at(i).y() * radius, diag, inv_mvmatrix, &tx, &ty, &tz); gluProject(tx, ty, tz, modelview_matrix, projection_matrix, viewport, &a, &b, &c); /** calcs the near point of the line */ fastMultiply(points->at(i).x() * radius, points->at(i).y() * radius, 0, inv_mvmatrix, &tx2, &ty2, &tz2); gluProject(tx2, ty2, tz2, modelview_matrix, projection_matrix, viewport, &a2, &b2, &c2); double da = (a - a2);// /(double)STEPS; double db = (b - b2);// /(double)STEPS; double dc = (c - c2);// /(double)STEPS; double pix_x = a2; double pix_y = b2; double pix_z = c2; /** to search with quicksearch the nearest zbuffer value in the line */ for (int k = 0; k < STEPS; k++) { double inv_yy = gla->height() - pix_y; double zz = 999; if ((int)pix_x >= 0 && (int)pix_x < gla->width() && (int)pix_y >= 0 && (int)pix_y < gla->height()) zz = (GLfloat)pixels[(int)(((int)pix_y)*gla->width() + (int)pix_x)]; da = da / 2.0; db = db / 2.0; dc = dc / 2.0; if (std::abs(zz - pix_z) < 0.001) { proj_points[i] = QPointF(pix_x, inv_yy); break; } else if (zz > pix_z) { pix_x += da; pix_y += db; pix_z += dc; } else { pix_x -= da; pix_y -= db; pix_z -= dc; } if (k == STEPS - 1) { proj_points[i] = QPointF(pix_x, inv_yy); break; } } } glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0, gla->width(), gla->height(), 0, -1, 1); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glPushAttrib(GL_ENABLE_BIT); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glEnable(GL_COLOR_LOGIC_OP); glLogicOp(GL_XOR); glColor3f(1, 1, 1); /** draws the circle */ glBegin(GL_LINE_LOOP); for (unsigned int i = 0; i < points->size(); i++) { glVertex2f(proj_points[i].x(), proj_points[i].y()); } glEnd(); glDisable(GL_COLOR_LOGIC_OP); glPopAttrib(); glPopMatrix(); // restore modelview glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } //each side is divided in given segments void generatePolygon(std::vector & vertices, int sides, int segments) { float step = sides / 2.0; float start_angle = M_PI / sides; for (int k = 0; k < sides; k++) { vertices.push_back(QPointF(sin((M_PI * (float)k / step) + start_angle), cos((M_PI * (float)k / step) + start_angle))); } if (segments > 1) { int sk; for (int k = 0; k < sides; k++) { sk = (k + 1) % sides; QPointF kv = vertices.at(k); QPointF skv = vertices.at(sk); QPointF d = (skv - kv) / segments; vertices.push_back(kv); for (int j = 1; j < segments; j++) { vertices.push_back(kv + d * j); } } vertices.erase(vertices.begin(), vertices.begin() + sides); } } //TODO This should be done statically /** * Generates the same circle points that Gfrei's algorithm does */ void generateCircle(std::vector & vertices, int segments) { return generatePolygon(vertices, segments, 1); } void generateSquare(std::vector & vertices, int segments) { return generatePolygon(vertices, 4, segments); }