diff --git a/MM.h b/MM.h index de1faf3..f58601f 100644 --- a/MM.h +++ b/MM.h @@ -74,6 +74,9 @@ public: static const qreal ZOOM_FACTOR; static const qreal ZOOM_MIN; static const qreal ZOOM_MAX; + + // Misc. + static const int MESH_SUBDIVISION_MIN_AREA = 400; }; #endif diff --git a/MainWindow.cpp b/MainWindow.cpp index 287aaae..bcc2c37 100644 --- a/MainWindow.cpp +++ b/MainWindow.cpp @@ -2744,6 +2744,7 @@ bool MainWindow::setTextureVolume(int texture_id, double volume) if (paint->getType() == "media") { Media *media = static_cast(paint.data()); // FIXME: use sharedptr cast + //Media *media = (Media *) paint.data(); // FIXME: use sharedptr cast videoTimer->stop(); media->setVolume(volume); videoTimer->start(); diff --git a/MainWindow.h b/MainWindow.h index 90d61f1..e881907 100644 --- a/MainWindow.h +++ b/MainWindow.h @@ -89,6 +89,7 @@ private slots: void openRecentFile(); void clearRecentFileList(); void openRecentVideo(); + void quitMapMap(); // Edit menu. void deleteItem(); // Context menu for mappings. diff --git a/MapperGLCanvas.h b/MapperGLCanvas.h index 9829b3b..0124686 100644 --- a/MapperGLCanvas.h +++ b/MapperGLCanvas.h @@ -83,6 +83,9 @@ public: /// Set the currently active vertex void setActiveVertexIndex(int activeVertex) { _activeVertex = activeVertex; } + bool shapeGrabbed() const { return _shapeGrabbed; } + bool vertexGrabbed() const { return _vertexGrabbed; } + qreal getZoomFactor() const { return qBound(qPow(MM::ZOOM_FACTOR, _zoomLevel), MM::ZOOM_MIN, MM::ZOOM_MAX); } /// This function needs to be called after a shape inside the canvas has been changed for appropriate signals to be activated. diff --git a/ShapeGraphicsItem.cpp b/ShapeGraphicsItem.cpp index 4fea58f..5c8bc10 100644 --- a/ShapeGraphicsItem.cpp +++ b/ShapeGraphicsItem.cpp @@ -309,6 +309,12 @@ PolygonTextureGraphicsItem::PolygonTextureGraphicsItem(Mapping::ptr mapping, boo _controlPainter.reset(new PolygonControlPainter(this)); } +MeshTextureGraphicsItem::MeshTextureGraphicsItem(Mapping::ptr mapping, bool output) : PolygonTextureGraphicsItem(mapping, output) { + _controlPainter.reset(new MeshControlPainter(this)); + _nHorizontalQuads = _nVerticalQuads = -1; + _wasGrabbing = false; +} + void MeshTextureGraphicsItem::_doDrawOutput(QPainter* painter) { Q_UNUSED(painter); @@ -318,61 +324,123 @@ void MeshTextureGraphicsItem::_doDrawOutput(QPainter* painter) QSharedPointer inputMesh = qSharedPointerCast(_inputShape); QVector > outputQuads = outputMesh->getQuads2d(); QVector > inputQuads = inputMesh->getQuads2d(); + + // Check if we increased or decreased number of columns/rows in mesh. + bool forceRebuild = false; + if (_nHorizontalQuads != outputMesh->nHorizontalQuads() || + _nVerticalQuads != outputMesh->nVerticalQuads()) + { + forceRebuild = true; + _cachedQuadItems.resize(_nHorizontalQuads = outputMesh->nHorizontalQuads()); + for (int i=0; i<_nHorizontalQuads; i++) + _cachedQuadItems[i].resize(_nVerticalQuads = outputMesh->nVerticalQuads()); + } + + // Keep track of whether we are currently grabbing the shape or a vertex so as to + // reduce resolution when editing (to prevent lags). + bool grabbing = (getCanvas()->shapeGrabbed() || getCanvas()->vertexGrabbed()); + + // Max depth is adjusted to draw less quads during click & drag. + int maxDepth = (grabbing ? 4 : -1); + + // Force rebuild on shape/vertex release. + if (_wasGrabbing && !grabbing) { + forceRebuild = true; + } + _wasGrabbing = grabbing; + + // Go through the mesh quad by quad. for (int x = 0; x < outputMesh->nHorizontalQuads(); x++) { for (int y = 0; y < outputMesh->nVerticalQuads(); y++) { - QSizeF size = mapFromScene(outputQuads[x][y].toPolygon()).boundingRect().size(); - float area = size.width() * size.height(); - _drawQuad(*_texture.toStrongRef(), inputQuads[x][y], outputQuads[x][y], area); + Quad& inputQuad = inputQuads[x][y]; + Quad& outputQuad = outputQuads[x][y]; + + // Verify if item needs recomputing. + CacheQuadItem& item = _cachedQuadItems[x][y]; + if (forceRebuild || + item.parent.input.toPolygon() != inputQuad.toPolygon() || + item.parent.output.toPolygon() != outputQuad.toPolygon()) { + + // Copy input and output quads for verification purposes. + item.parent.input = inputQuad; + item.parent.output = outputQuad; + + // Recompute sub quads. + item.subQuads.clear(); + + QSizeF size = mapFromScene(outputQuad.toPolygon()).boundingRect().size(); + float area = size.width() * size.height(); + + // Rebuild cache quad item. + _buildCacheQuadItem(item, inputQuad, outputQuad, area, 0.0001f, 0.001f, MM::MESH_SUBDIVISION_MIN_AREA, maxDepth); + } + + // Draw all the cached items. + foreach (CacheQuadMapping m, item.subQuads) + { + glBegin(GL_QUADS); + for (int i = 0; i < outputQuad.nVertices(); i++) + { + Util::setGlTexPoint(*_texture.toStrongRef(), m.input.getVertex(i), mapFromScene(m.output.getVertex(i))); + } + glEnd(); + } } } } - } -MeshTextureGraphicsItem::MeshTextureGraphicsItem(Mapping::ptr mapping, bool output) : PolygonTextureGraphicsItem(mapping, output) { - _controlPainter.reset(new MeshControlPainter(this)); -} - - -void MeshTextureGraphicsItem::_drawQuad(const Texture& texture, const Quad& inputQuad, const Quad& outputQuad, float outputArea, float inputThreshod, float outputThreshold) +void MeshTextureGraphicsItem::_buildCacheQuadItem(CacheQuadItem& item, const Quad& inputQuad, const Quad& outputQuad, float outputArea, float inputThreshod, float outputThreshold, int minArea, int maxDepth) { - QPointF oa = mapFromScene(outputQuad.getVertex(0)); - QPointF ob = mapFromScene(outputQuad.getVertex(1)); - QPointF oc = mapFromScene(outputQuad.getVertex(2)); - QPointF od = mapFromScene(outputQuad.getVertex(3)); + bool stop = false; + if (maxDepth == 0 || outputArea < minArea) + stop = true; + else { + QPointF oa = mapFromScene(outputQuad.getVertex(0)); + QPointF ob = mapFromScene(outputQuad.getVertex(1)); + QPointF oc = mapFromScene(outputQuad.getVertex(2)); + QPointF od = mapFromScene(outputQuad.getVertex(3)); - QPointF ia = inputQuad.getVertex(0); - QPointF ib = inputQuad.getVertex(1); - QPointF ic = inputQuad.getVertex(2); - QPointF id = inputQuad.getVertex(3); + QPointF ia = inputQuad.getVertex(0); + QPointF ib = inputQuad.getVertex(1); + QPointF ic = inputQuad.getVertex(2); + QPointF id = inputQuad.getVertex(3); - // compute the dot products for the polygon - float outputV1dotV2 = QPointF::dotProduct(oa-ob, oc-ob); - float outputV3dotV4 = QPointF::dotProduct(oc-od, oa-od); - float outputV1dotV4 = QPointF::dotProduct(oa-ob, oa-od); - float outputV2dotV3 = QPointF::dotProduct(oc-ob, oc-od); + QPointF outputV1 = oa-ob; + QPointF outputV2 = oc-ob; + QPointF outputV3 = oc-od; + QPointF outputV4 = oa-od; - // compute the dot products for the texture - float inputV1dotV2 = QPointF::dotProduct(ia-ib, ic-ib); - float inputV3dotV4 = QPointF::dotProduct(ic-id, ia-id); - float inputV1dotV4 = QPointF::dotProduct(ia-ib, ia-id); - float inputV2dotV3 = QPointF::dotProduct(ic-ib, ic-id); + QPointF inputV1 = ia-ib; + QPointF inputV2 = ic-ib; + QPointF inputV3 = ic-id; + QPointF inputV4 = ia-id; - // Stopping criterion. - if (outputArea < 200 || - (fabs(outputV1dotV2 - outputV3dotV4) < outputThreshold && - fabs(outputV1dotV4 - outputV2dotV3) < outputThreshold && - fabs(inputV1dotV2 - inputV3dotV4) < inputThreshod && - fabs(inputV1dotV4 - inputV2dotV3) < inputThreshod)) + // compute the dot products for the polygon + float outputV1dotV2 = QPointF::dotProduct(outputV1, outputV2); + float outputV3dotV4 = QPointF::dotProduct(outputV3, outputV4); + float outputV1dotV4 = QPointF::dotProduct(outputV1, outputV4); + float outputV2dotV3 = QPointF::dotProduct(outputV2, outputV3); + + // compute the dot products for the texture + float inputV1dotV2 = QPointF::dotProduct(inputV1, inputV2); + float inputV3dotV4 = QPointF::dotProduct(inputV3, inputV4); + float inputV1dotV4 = QPointF::dotProduct(inputV1, inputV4); + float inputV2dotV3 = QPointF::dotProduct(inputV2, inputV3); + + // Stopping criterion. + stop = (fabs(outputV1dotV2 - outputV3dotV4) < outputThreshold && + fabs(outputV1dotV4 - outputV2dotV3) < outputThreshold && + fabs(inputV1dotV2 - inputV3dotV4) < inputThreshod && + fabs(inputV1dotV4 - inputV2dotV3) < inputThreshod); + } + + // + if (stop) { - glBegin(GL_QUADS); - for (int i = 0; i < outputQuad.nVertices(); i++) - { - Util::setGlTexPoint(texture, inputQuad.getVertex(i), mapFromScene(outputQuad.getVertex(i))); - } - glEnd(); + item.subQuads.append( (CacheQuadMapping){ inputQuad, outputQuad } ); } else // subdivide { @@ -380,7 +448,7 @@ void MeshTextureGraphicsItem::_drawQuad(const Texture& texture, const Quad& inpu QList outputSubQuads = _split(outputQuad); for (int i = 0; i < inputSubQuads.size(); i++) { - _drawQuad(texture, inputSubQuads[i], outputSubQuads[i], outputArea*0.25, inputThreshod, outputThreshold); + _buildCacheQuadItem(item, inputSubQuads[i], outputSubQuads[i], outputArea*0.25, inputThreshod, outputThreshold, minArea, (maxDepth == -1 ? -1 : maxDepth - 1)); } } } diff --git a/ShapeGraphicsItem.h b/ShapeGraphicsItem.h index f372d24..fc20b20 100644 --- a/ShapeGraphicsItem.h +++ b/ShapeGraphicsItem.h @@ -201,9 +201,22 @@ public: }; -/// Graphics item for textured mesh. +/** + * Graphics item for textured mesh. + * The drawing technique recursively subdivides the quad to approximate projective mapping and thus + * avoiding artifacts on the diagonals. Subdivided structure is cached to increase performance. + * Source: Oliveira, M. "Correcting Texture Mapping Errors Introduced by Graphics Hardware" + */ class MeshTextureGraphicsItem : public PolygonTextureGraphicsItem { + struct CacheQuadMapping { + Quad input; + Quad output; + }; + struct CacheQuadItem { + CacheQuadMapping parent; + QList subQuads; + }; public: MeshTextureGraphicsItem(Mapping::ptr mapping, bool output=true); virtual ~MeshTextureGraphicsItem(){} @@ -212,8 +225,15 @@ public: private: // Draws quad recursively using the technique described in Oliveira, M. "Correcting Texture Mapping Errors Introduced by Graphics Hardware" - void _drawQuad(const Texture& texture, const Quad& inputQuad, const Quad& outputQuad, float outputArea, float inputThreshold = 0.0001f, float outputThreshold = 0.001f); + void _buildCacheQuadItem(CacheQuadItem& item, const Quad& inputQuad, const Quad& outputQuad, + float outputArea, float inputThreshold = 0.0001f, float outputThreshold = 0.001f, + int minArea=MM::MESH_SUBDIVISION_MIN_AREA, int maxDepth=-1); QList _split(const Quad& quad); + + QVector > _cachedQuadItems; + int _nHorizontalQuads; + int _nVerticalQuads; + bool _wasGrabbing; }; /// Graphics item for textured mesh.