From 9f55f738f32501d5bbae58031ed662af6122a853 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Wed, 25 Dec 2019 08:54:49 +0930 Subject: [PATCH] Implement marker pen This commit introduce a new feature: Marker Pen It allow user to quick draw a contour of the side view and got nodes automatically generated --- ACKNOWLEDGEMENTS.html | 6 + docs/shortcuts.rst | 2 + dust3d.pro | 3 + languages/dust3d_zh_CN.ts | 4 + src/contourtopartconverter.cpp | 260 ++++++++++++++++++++++++++++++ src/contourtopartconverter.h | 40 +++++ src/document.cpp | 30 +++- src/document.h | 2 + src/documentwindow.cpp | 10 ++ src/gridmeshbuilder.cpp | 2 +- src/imageskeletonextractor.cpp | 172 +++++++++++++++++++- src/imageskeletonextractor.h | 8 +- src/main.cpp | 8 +- src/materialpreviewsgenerator.cpp | 54 ++++--- src/meshresultpostprocessor.cpp | 3 + src/meshwrapper.cpp | 2 +- src/shortcuts.cpp | 1 + src/skeletondocument.h | 1 + src/skeletongraphicswidget.cpp | 99 ++++++++++-- src/skeletongraphicswidget.h | 66 ++++++++ src/strokemodifier.cpp | 4 +- src/util.cpp | 10 +- src/util.h | 2 +- 23 files changed, 724 insertions(+), 65 deletions(-) create mode 100644 src/contourtopartconverter.cpp create mode 100644 src/contourtopartconverter.h diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html index 563696b3..354bd46d 100644 --- a/ACKNOWLEDGEMENTS.html +++ b/ACKNOWLEDGEMENTS.html @@ -1218,4 +1218,10 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode

Coons patch

     https://en.wikipedia.org/wiki/Coons_patch
+
+ +

T. Y. ZHANG and C. Y. SUEN

+
+    A Fast Parallel Algorithm for Thinning Digital Patterns
+    https://dl.acm.org/citation.cfm?id=358023
 
\ No newline at end of file diff --git a/docs/shortcuts.rst b/docs/shortcuts.rst index 85169037..72aa35ed 100644 --- a/docs/shortcuts.rst +++ b/docs/shortcuts.rst @@ -34,6 +34,8 @@ Keyboard +----------------------+--------------------------------------------------------------------------+ | D | Enter Paint Mode | +----------------------+--------------------------------------------------------------------------+ +| G | Enter Mark Mode | ++----------------------+--------------------------------------------------------------------------+ | CTRL + S | Save | +----------------------+--------------------------------------------------------------------------+ | CTRL + Z | Undo | diff --git a/dust3d.pro b/dust3d.pro index b220abc7..6103a604 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -480,6 +480,9 @@ HEADERS += src/booleanmesh.h SOURCES += src/imageskeletonextractor.cpp HEADERS += src/imageskeletonextractor.h +SOURCES += src/contourtopartconverter.cpp +HEADERS += src/contourtopartconverter.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index 050df67c..989504f7 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -386,6 +386,10 @@ Tips: Create Wrap Parts 创建包裹部件 + + Marker pen + 马克笔 + ExportPreviewWidget diff --git a/src/contourtopartconverter.cpp b/src/contourtopartconverter.cpp new file mode 100644 index 00000000..2bfbb185 --- /dev/null +++ b/src/contourtopartconverter.cpp @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +#include +#include "contourtopartconverter.h" +#include "imageskeletonextractor.h" +#include "util.h" + +const float ContourToPartConverter::m_targetImageHeight = 64.0f; +const float ContourToPartConverter::m_minEdgeLength = 0.025; +const float ContourToPartConverter::m_radiusEpsilon = 0.0025; + +ContourToPartConverter::ContourToPartConverter(const QPolygonF &mainProfile, + const QPolygonF &sideProfile, const QSizeF &canvasSize) : + m_mainProfile(mainProfile), + m_sideProfile(sideProfile), + m_canvasSize(canvasSize) +{ +} + +void ContourToPartConverter::process() +{ + convert(); + emit finished(); +} + +const Snapshot &ContourToPartConverter::getSnapshot() +{ + return m_snapshot; +} + +void ContourToPartConverter::extractSkeleton(const QPolygonF &polygon, + std::vector> *skeleton) +{ + auto originalBoundingBox = polygon.boundingRect(); + QPointF polygonTopLeft = originalBoundingBox.topLeft(); + + float scaleFactor = m_targetImageHeight / originalBoundingBox.height(); + + QTransform transform; + transform = transform.scale(scaleFactor, scaleFactor); + QPolygonF scaledPolygon = transform.map(polygon); + + QRectF boundingBox = scaledPolygon.boundingRect(); + QImage *image = new QImage(boundingBox.width() + 4, boundingBox.height() + 4, QImage::Format_Grayscale8); + image->fill(QColor(255, 255, 255)); + + qreal offsetX = 1 - boundingBox.left(); + qreal offsetY = 1 - boundingBox.top(); + + QPainterPath path; + path.addPolygon(scaledPolygon.translated(offsetX, offsetY)); + + QPainter painter; + painter.begin(image); + painter.setPen(Qt::PenStyle::NoPen); + painter.fillPath(path, Qt::black); + painter.end(); + + ImageSkeletonExtractor imageSkeletonExtractor; + imageSkeletonExtractor.setImage(image); + imageSkeletonExtractor.extract(); + + std::vector> imageSkeleton; + int imageArea = imageSkeletonExtractor.getArea(); + imageSkeletonExtractor.getSkeleton(&imageSkeleton); + const std::set> &blackPixels = imageSkeletonExtractor.getBlackPixels(); + + std::vector> selectedNodes; + if (imageSkeleton.size() >= 2) { + int targetLength = std::ceil(0.5 * (float)imageArea / imageSkeleton.size()); + int minLength = m_minEdgeLength * imageSkeleton.size(); + if (targetLength < minLength) + targetLength = minLength; + size_t newInsertNum = imageSkeleton.size() / targetLength; + if (newInsertNum < 1) + newInsertNum = 1; + if (newInsertNum > 100) + newInsertNum = 100; + float stepFactor = 1.0 / (newInsertNum + 1); + float factor = stepFactor; + selectedNodes.push_back(imageSkeleton[0]); + for (size_t i = 0; i < newInsertNum && factor < 1.0; factor += stepFactor, ++i) { + size_t index = factor * imageSkeleton.size(); + if (index <= 0 || index >= imageSkeleton.size()) + continue; + selectedNodes.push_back(imageSkeleton[index]); + } + selectedNodes.push_back(imageSkeleton[imageSkeleton.size() - 1]); + } + + std::vector selectedPositions; + selectedPositions.reserve(selectedNodes.size()); + for (const auto &it: selectedNodes) + selectedPositions.push_back(QVector3D(it.first, it.second, 0.0f)); + + std::vector selectedDirections; + selectedDirections.reserve(selectedNodes.size()); + for (size_t i = 0; i < selectedPositions.size(); ++i) { + QVector3D sumOfDirections; + if (i > 0) { + sumOfDirections += selectedPositions[i] - selectedPositions[i - 1]; + } + if (i + 1 < selectedPositions.size()) { + sumOfDirections += selectedPositions[i + 1] - selectedPositions[i]; + } + selectedDirections.push_back(sumOfDirections.normalized()); + } + + std::vector selectedRadius; + selectedRadius.reserve(selectedNodes.size()); + for (size_t i = 0; i < selectedDirections.size(); ++i) { + selectedRadius.push_back(calculateNodeRadius(selectedPositions[i], selectedDirections[i], + blackPixels)); + } + + skeleton->resize(selectedRadius.size()); + auto canvasHeight = m_canvasSize.height(); + for (size_t i = 0; i < skeleton->size(); ++i) { + const auto &node = selectedNodes[i]; + (*skeleton)[i] = std::make_pair(QVector2D(node.first / scaleFactor + polygonTopLeft.x(), + node.second / scaleFactor + polygonTopLeft.y()) / canvasHeight, + selectedRadius[i] / scaleFactor / canvasHeight); + } +} + +int ContourToPartConverter::calculateNodeRadius(const QVector3D &node, + const QVector3D &direction, + const std::set> &black) +{ + const QVector3D pointer = {0.0f, 0.0f, 1.0f}; + QVector3D offsetDirection = QVector3D::crossProduct(direction, pointer); + int radius = 1; + while (true) { + QVector3D offset = radius * offsetDirection; + QVector3D sidePosition = node + offset; + QVector3D otherSidePosition = node - offset; + if (black.find(std::make_pair((int)sidePosition.x(), (int)sidePosition.y())) == black.end()) + break; + if (black.find(std::make_pair((int)otherSidePosition.x(), (int)otherSidePosition.y())) == black.end()) + break; + ++radius; + } + return radius; +} + +void ContourToPartConverter::smoothRadius(std::vector> *skeleton) +{ + if (skeleton->empty()) + return; + + std::vector newRadius; + newRadius.reserve(skeleton->size() + 2); + newRadius.push_back(skeleton->front().second); + for (const auto &it: (*skeleton)) { + newRadius.push_back(it.second); + } + newRadius.push_back(skeleton->back().second); + for (size_t h = 0; h < skeleton->size(); ++h) { + size_t i = h + 1; + size_t j = h + 2; + (*skeleton)[h].second = (newRadius[h] + + newRadius[i] + newRadius[j]) / 3; + } +} + +void ContourToPartConverter::optimizeNodes() +{ + auto oldNodes = m_nodes; + m_nodes.clear(); + m_nodes.reserve(oldNodes.size()); + for (size_t i = 0; i < oldNodes.size(); ++i) { + if (i > 0 && i + 1 < oldNodes.size()) { + size_t h = i - 1; + size_t j = i + 1; + if (std::abs(oldNodes[i].second - oldNodes[h].second) < m_radiusEpsilon && + std::abs(oldNodes[i].second - oldNodes[j].second) < m_radiusEpsilon) { + auto degrees = degreesBetweenVectors((oldNodes[i].first - oldNodes[h].first).normalized(), + (oldNodes[j].first - oldNodes[i].first).normalized()); + if (degrees < 15) + continue; + } + } + m_nodes.push_back(oldNodes[i]); + } +} + +void ContourToPartConverter::convert() +{ + std::vector> sideSkeleton; + auto mainBoundingBox = m_mainProfile.boundingRect(); + extractSkeleton(m_sideProfile, &sideSkeleton); + if (!sideSkeleton.empty()) { + float x = mainBoundingBox.center().x() / m_canvasSize.height(); + m_nodes.reserve(sideSkeleton.size()); + for (size_t i = 0; i < sideSkeleton.size(); ++i) { + const auto &it = sideSkeleton[i]; + m_nodes.push_back(std::make_pair(QVector3D(x, it.first.y(), it.first.x()), + it.second)); + } + } + optimizeNodes(); + nodesToSnapshot(); +} + +void ContourToPartConverter::nodesToSnapshot() +{ + if (m_nodes.empty()) + return; + + auto partId = QUuid::createUuid(); + auto partIdString = partId.toString(); + std::map snapshotPart; + snapshotPart["id"] = partIdString; + snapshotPart["subdived"] = "true"; + snapshotPart["rounded"] = "true"; + m_snapshot.parts[partIdString] = snapshotPart; + + auto componentId = QUuid::createUuid(); + auto componentIdString = componentId.toString(); + std::map snapshotComponent; + snapshotComponent["id"] = componentIdString; + snapshotComponent["combineMode"] = "Normal"; + snapshotComponent["linkDataType"] = "partId"; + snapshotComponent["linkData"] = partIdString; + m_snapshot.components[componentIdString] = snapshotComponent; + + m_snapshot.rootComponent["children"] = componentIdString; + + std::vector nodeIdStrings; + nodeIdStrings.reserve(m_nodes.size()); + for (const auto &it: m_nodes) { + auto nodeId = QUuid::createUuid(); + auto nodeIdString = nodeId.toString(); + std::map snapshotNode; + snapshotNode["id"] = nodeIdString; + snapshotNode["x"] = QString::number(it.first.x()); + snapshotNode["y"] = QString::number(it.first.y()); + snapshotNode["z"] = QString::number(it.first.z()); + snapshotNode["radius"] = QString::number(it.second); + snapshotNode["partId"] = partIdString; + m_snapshot.nodes[nodeIdString] = snapshotNode; + nodeIdStrings.push_back(nodeIdString); + } + + for (size_t i = 1; i < nodeIdStrings.size(); ++i) { + size_t h = i - 1; + auto edgeId = QUuid::createUuid(); + auto edgeIdString = edgeId.toString(); + std::map snapshotEdge; + snapshotEdge["id"] = edgeIdString; + snapshotEdge["from"] = nodeIdStrings[h]; + snapshotEdge["to"] = nodeIdStrings[i]; + snapshotEdge["partId"] = partIdString; + m_snapshot.edges[edgeIdString] = snapshotEdge; + } +} + diff --git a/src/contourtopartconverter.h b/src/contourtopartconverter.h new file mode 100644 index 00000000..e9d904b9 --- /dev/null +++ b/src/contourtopartconverter.h @@ -0,0 +1,40 @@ +#ifndef DUST3D_CONTOUR_TO_PART_CONVERTER_H +#define DUST3D_CONTOUR_TO_PART_CONVERTER_H +#include +#include +#include +#include +#include +#include "snapshot.h" + +class ContourToPartConverter : public QObject +{ + Q_OBJECT +public: + ContourToPartConverter(const QPolygonF &mainProfile, const QPolygonF &sideProfile, const QSizeF &canvasSize); + const Snapshot &getSnapshot(); +signals: + void finished(); +public slots: + void process(); +private: + QPolygonF m_mainProfile; + QPolygonF m_sideProfile; + QSizeF m_canvasSize; + Snapshot m_snapshot; + static const float m_targetImageHeight; + static const float m_minEdgeLength; + static const float m_radiusEpsilon; + std::vector> m_nodes; + void convert(); + void extractSkeleton(const QPolygonF &polygon, + std::vector> *skeleton); + int calculateNodeRadius(const QVector3D &node, + const QVector3D &direction, + const std::set> &black); + void nodesToSnapshot(); + void smoothRadius(std::vector> *skeleton); + void optimizeNodes(); +}; + +#endif diff --git a/src/document.cpp b/src/document.cpp index 23ba69d9..36254f47 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -19,6 +19,7 @@ #include "scriptrunner.h" #include "mousepicker.h" #include "imageforever.h" +#include "contourtopartconverter.h" unsigned long Document::m_maxSnapshot = 1000; @@ -324,6 +325,28 @@ void Document::addNode(float x, float y, float z, float radius, QUuid fromNodeId createNode(QUuid::createUuid(), x, y, z, radius, fromNodeId); } +void Document::addPartByPolygons(const QPolygonF &mainProfile, const QPolygonF &sideProfile, const QSizeF &canvasSize) +{ + if (mainProfile.empty() || sideProfile.empty()) + return; + + QThread *thread = new QThread; + ContourToPartConverter *contourToPartConverter = new ContourToPartConverter(mainProfile, sideProfile, canvasSize); + contourToPartConverter->moveToThread(thread); + connect(thread, &QThread::started, contourToPartConverter, &ContourToPartConverter::process); + connect(contourToPartConverter, &ContourToPartConverter::finished, this, [=]() { + const auto &snapshot = contourToPartConverter->getSnapshot(); + if (!snapshot.nodes.empty()) { + addFromSnapshot(snapshot, true); + saveSnapshot(); + } + delete contourToPartConverter; + }); + connect(contourToPartConverter, &ContourToPartConverter::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + void Document::addNodeWithId(QUuid nodeId, float x, float y, float z, float radius, QUuid fromNodeId) { createNode(nodeId, x, y, z, radius, fromNodeId); @@ -1522,7 +1545,12 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) part.id = newUuid; oldNewIdMap[QUuid(partKv.first)] = part.id; part.name = valueOfKeyInMapOrEmpty(partKv.second, "name"); - part.visible = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "visible")); + const auto &visibleIt = partKv.second.find("visible"); + if (visibleIt != partKv.second.end()) { + part.visible = isTrueValueString(visibleIt->second); + } else { + part.visible = true; + } part.locked = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "locked")); part.subdived = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "subdived")); part.disabled = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "disabled")); diff --git a/src/document.h b/src/document.h index e1c140a0..ad420862 100644 --- a/src/document.h +++ b/src/document.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "snapshot.h" #include "meshloader.h" #include "meshgenerator.h" @@ -567,6 +568,7 @@ public slots: void removeNode(QUuid nodeId); void removeEdge(QUuid edgeId); void removePart(QUuid partId); + void addPartByPolygons(const QPolygonF &mainProfile, const QPolygonF &sideProfile, const QSizeF &canvasSize); void addNodeWithId(QUuid nodeId, float x, float y, float z, float radius, QUuid fromNodeId); void addNode(float x, float y, float z, float radius, QUuid fromNodeId); void scaleNodeByAddRadius(QUuid nodeId, float amount); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 43bb2085..788005c9 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -160,6 +160,10 @@ DocumentWindow::DocumentWindow() : selectButton->setToolTip(tr("Select node on canvas")); Theme::initAwesomeButton(selectButton); + QPushButton *markerButton = new QPushButton(QChar(fa::edit)); + markerButton->setToolTip(tr("Marker pen")); + Theme::initAwesomeButton(markerButton); + QPushButton *paintButton = new QPushButton(QChar(fa::paintbrush)); paintButton->setToolTip(tr("Paint brush")); Theme::initAwesomeButton(paintButton); @@ -240,6 +244,7 @@ DocumentWindow::DocumentWindow() : toolButtonLayout->addWidget(addButton); toolButtonLayout->addWidget(selectButton); + toolButtonLayout->addWidget(markerButton); toolButtonLayout->addWidget(paintButton); //toolButtonLayout->addWidget(dragButton); toolButtonLayout->addWidget(zoomInButton); @@ -853,6 +858,10 @@ DocumentWindow::DocumentWindow() : m_document->setEditMode(SkeletonDocumentEditMode::Select); }); + connect(markerButton, &QPushButton::clicked, [=]() { + m_document->setEditMode(SkeletonDocumentEditMode::Mark); + }); + connect(paintButton, &QPushButton::clicked, [=]() { m_document->setEditMode(SkeletonDocumentEditMode::Paint); }); @@ -942,6 +951,7 @@ DocumentWindow::DocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartCutRotation); connect(graphicsWidget, &SkeletonGraphicsWidget::createGriddedPartsFromNodes, m_document, &Document::createGriddedPartsFromNodes); + connect(graphicsWidget, &SkeletonGraphicsWidget::addPartByPolygons, m_document, &Document::addPartByPolygons); connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &Document::setXlockState); connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &Document::setYlockState); diff --git a/src/gridmeshbuilder.cpp b/src/gridmeshbuilder.cpp index e924adfe..e10539c4 100644 --- a/src/gridmeshbuilder.cpp +++ b/src/gridmeshbuilder.cpp @@ -61,7 +61,7 @@ void GridMeshBuilder::splitCycleToPolylines(const std::vector &cycle, size_t j = (i + 1) % cycle.size(); QVector3D hi = m_nodeVertices[cycle[i]].position - m_nodeVertices[cycle[h]].position; QVector3D ij = m_nodeVertices[cycle[j]].position - m_nodeVertices[cycle[i]].position; - auto angle = angleBetweenVectors(hi, ij); + auto angle = degreesBetweenVectors(hi, ij); //qDebug() << "angle[" << i << "]:" << angle; if (angle >= m_polylineAngleChangeThreshold) cornerIndices.push_back(i); diff --git a/src/imageskeletonextractor.cpp b/src/imageskeletonextractor.cpp index f00a234a..085988d9 100644 --- a/src/imageskeletonextractor.cpp +++ b/src/imageskeletonextractor.cpp @@ -1,11 +1,12 @@ +#include +#include +#include #include "imageskeletonextractor.h" // This is an implementation of the following paper: // // T. Y. ZHANG and C. Y. SUEN -const int ImageSkeletonExtractor::m_targetHeight = 256; - ImageSkeletonExtractor::~ImageSkeletonExtractor() { delete m_image; @@ -71,12 +72,34 @@ bool ImageSkeletonExtractor::secondSubiterationSatisfied(int i, int j) return true; } +void ImageSkeletonExtractor::calculateAreaAndBlackPixels() +{ + m_area = 0; + m_blackPixels.clear(); + for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) { + for (int j = 1; j < (int)m_grayscaleImage->height() - 1; ++j) { + if (isBlack(i, j)) { + ++m_area; + m_blackPixels.insert({i, j}); + } + } + } +} + +const std::set> &ImageSkeletonExtractor::getBlackPixels() +{ + return m_blackPixels; +} + +int ImageSkeletonExtractor::getArea() +{ + return m_area; +} + void ImageSkeletonExtractor::extract() { - m_grayscaleImage = new QImage(m_image->convertToFormat( - QImage::Format_Grayscale8).scaled( - QSize(m_targetHeight, m_targetHeight), Qt::KeepAspectRatio)); - + m_grayscaleImage = new QImage(m_image->convertToFormat(QImage::Format_Grayscale8)); + calculateAreaAndBlackPixels(); while (true) { std::vector> firstSatisfied; for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) { @@ -98,7 +121,140 @@ void ImageSkeletonExtractor::extract() setWhite(it.first, it.second); if (firstSatisfied.empty() && secondSatisfied.empty()) break; - printf("firstSatisfied:%d\r\n", firstSatisfied.size()); - printf("secondSatisfied:%d\r\n", secondSatisfied.size()); + } +} + +void ImageSkeletonExtractor::getSkeleton(std::vector> *skeleton) +{ + if (nullptr == m_grayscaleImage) + return; + + std::map, std::vector>> links; + for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) { + for (int j = 1; j < (int)m_grayscaleImage->height() - 1; ++j) { + if (!isBlack(i, j)) + continue; + auto ij = std::make_pair(i, j); + auto p2 = std::make_pair(i + neighborOffsets[P2].first, j + neighborOffsets[P2].second); + bool hasP3 = true; + bool hasP5 = true; + bool hasP7 = true; + bool hasP9 = true; + if (isBlack(p2.first, p2.second)) { + links[ij].push_back(p2); + hasP3 = false; + hasP9 = false; + } + auto p4 = std::make_pair(i + neighborOffsets[P4].first, j + neighborOffsets[P4].second); + if (isBlack(p4.first, p4.second)) { + links[ij].push_back(p4); + hasP3 = false; + hasP5 = false; + } + auto p6 = std::make_pair(i + neighborOffsets[P6].first, j + neighborOffsets[P6].second); + if (isBlack(p6.first, p6.second)) { + links[ij].push_back(p6); + hasP5 = false; + hasP7 = false; + } + auto p8 = std::make_pair(i + neighborOffsets[P8].first, j + neighborOffsets[P8].second); + if (isBlack(p8.first, p8.second)) { + links[ij].push_back(p8); + hasP7 = false; + hasP9 = false; + } + if (hasP3) { + auto p3 = std::make_pair(i + neighborOffsets[P3].first, j + neighborOffsets[P3].second); + if (isBlack(p3.first, p3.second)) { + links[ij].push_back(p3); + } + } + if (hasP5) { + auto p5 = std::make_pair(i + neighborOffsets[P5].first, j + neighborOffsets[P5].second); + if (isBlack(p5.first, p5.second)) { + links[ij].push_back(p5); + } + } + if (hasP7) { + auto p7 = std::make_pair(i + neighborOffsets[P7].first, j + neighborOffsets[P7].second); + if (isBlack(p7.first, p7.second)) { + links[ij].push_back(p7); + } + } + if (hasP9) { + auto p9 = std::make_pair(i + neighborOffsets[P9].first, j + neighborOffsets[P9].second); + if (isBlack(p9.first, p9.second)) { + links[ij].push_back(p9); + } + } + } + } + + auto calculateRouteLength = [&](const std::pair &branch, const std::pair &start) { + std::set> visited; + visited.insert(branch); + std::queue> waitPoints; + waitPoints.push(start); + size_t addLength = 0; + while (!waitPoints.empty()) { + auto point = waitPoints.front(); + waitPoints.pop(); + if (visited.find(point) != visited.end()) + continue; + visited.insert(point); + auto findLink = links.find(point); + if (findLink == links.end()) + break; + if (findLink->second.size() > 2) { + addLength = links.size(); // This will make sure the branch node is not been removed + break; + } + for (const auto &it: findLink->second) { + if (visited.find(it) != visited.end()) + continue; + waitPoints.push(it); + } + } + return visited.size() + addLength; + }; + for (auto &it: links) { + if (it.second.size() > 2) { + std::vector, size_t>> routes; + routes.reserve(it.second.size()); + for (size_t i = 0; i < it.second.size(); ++i) { + routes.push_back(std::make_pair(it.second[i], calculateRouteLength(it.first, it.second[i]))); + } + std::sort(routes.begin(), routes.end(), [](const std::pair, size_t> &first, + const std::pair, size_t> &second) { + return first.second < second.second; + }); + it.second = std::vector> {routes[routes.size() - 2].first, + routes[routes.size() - 1].first}; + } + } + + std::queue> waitPoints; + for (const auto &it: links) { + if (1 == it.second.size()) { + waitPoints.push(it.first); + break; + } + } + std::set> visited; + while (!waitPoints.empty()) { + auto point = waitPoints.front(); + waitPoints.pop(); + if (visited.find(point) != visited.end()) + continue; + visited.insert(point); + skeleton->push_back(point); + auto findLink = links.find(point); + if (findLink == links.end()) + break; + for (const auto &it: findLink->second) { + if (visited.find(it) != visited.end()) + continue; + waitPoints.push(it); + } } } diff --git a/src/imageskeletonextractor.h b/src/imageskeletonextractor.h index 8792375a..2cc85e89 100644 --- a/src/imageskeletonextractor.h +++ b/src/imageskeletonextractor.h @@ -3,6 +3,7 @@ #include #include #include +#include class ImageSkeletonExtractor : QObject { @@ -33,10 +34,14 @@ public: void setImage(QImage *image); void extract(); QImage *takeResultGrayscaleImage(); + void getSkeleton(std::vector> *skeleton); + int getArea(); + const std::set> &getBlackPixels(); private: QImage *m_image = nullptr; QImage *m_grayscaleImage = nullptr; - static const int m_targetHeight; + int m_area = 0; + std::set> m_blackPixels; bool isBlack(int i, int j) { @@ -78,6 +83,7 @@ private: bool firstSubiterationSatisfied(int i, int j); bool secondSubiterationSatisfied(int i, int j); + void calculateAreaAndBlackPixels(); }; #endif diff --git a/src/main.cpp b/src/main.cpp index 1882165b..40cbca34 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,10 +19,10 @@ int main(int argc, char ** argv) if (translator.load(QLocale(), QLatin1String("dust3d"), QLatin1String("_"), QLatin1String(":/languages"))) app.installTranslator(&translator); - QSurfaceFormat format = QSurfaceFormat::defaultFormat(); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); - format.setVersion(3, 3); - QSurfaceFormat::setDefaultFormat(format); + //QSurfaceFormat format = QSurfaceFormat::defaultFormat(); + //format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile); + //format.setVersion(3, 3); + //QSurfaceFormat::setDefaultFormat(format); // QuantumCD/Qt 5 Dark Fusion Palette // https://gist.github.com/QuantumCD/6245215 diff --git a/src/materialpreviewsgenerator.cpp b/src/materialpreviewsgenerator.cpp index 585c7e31..3cb3e94e 100644 --- a/src/materialpreviewsgenerator.cpp +++ b/src/materialpreviewsgenerator.cpp @@ -73,35 +73,37 @@ void MaterialPreviewsGenerator::generate() delete poseProcessor; } - for (const auto &material: m_materials) { - TextureGenerator *textureGenerator = new TextureGenerator(*outcome); - for (const auto &layer: material.second) { - for (const auto &mapItem: layer.maps) { - const QImage *image = ImageForever::get(mapItem.imageId); - if (nullptr == image) - continue; - for (const auto &partId: partIds) { - if (TextureType::BaseColor == mapItem.forWhat) - textureGenerator->addPartColorMap(partId, image, layer.tileScale); - else if (TextureType::Normal == mapItem.forWhat) - textureGenerator->addPartNormalMap(partId, image, layer.tileScale); - else if (TextureType::Metalness == mapItem.forWhat) - textureGenerator->addPartMetalnessMap(partId, image, layer.tileScale); - else if (TextureType::Roughness == mapItem.forWhat) - textureGenerator->addPartRoughnessMap(partId, image, layer.tileScale); - else if (TextureType::AmbientOcclusion == mapItem.forWhat) - textureGenerator->addPartAmbientOcclusionMap(partId, image, layer.tileScale); + if (nullptr != outcome) { + for (const auto &material: m_materials) { + TextureGenerator *textureGenerator = new TextureGenerator(*outcome); + for (const auto &layer: material.second) { + for (const auto &mapItem: layer.maps) { + const QImage *image = ImageForever::get(mapItem.imageId); + if (nullptr == image) + continue; + for (const auto &partId: partIds) { + if (TextureType::BaseColor == mapItem.forWhat) + textureGenerator->addPartColorMap(partId, image, layer.tileScale); + else if (TextureType::Normal == mapItem.forWhat) + textureGenerator->addPartNormalMap(partId, image, layer.tileScale); + else if (TextureType::Metalness == mapItem.forWhat) + textureGenerator->addPartMetalnessMap(partId, image, layer.tileScale); + else if (TextureType::Roughness == mapItem.forWhat) + textureGenerator->addPartRoughnessMap(partId, image, layer.tileScale); + else if (TextureType::AmbientOcclusion == mapItem.forWhat) + textureGenerator->addPartAmbientOcclusionMap(partId, image, layer.tileScale); + } } } + textureGenerator->generate(); + MeshLoader *texturedResultMesh = textureGenerator->takeResultMesh(); + if (nullptr != texturedResultMesh) { + m_previews[material.first] = new MeshLoader(*texturedResultMesh); + m_generatedMaterialIds.insert(material.first); + delete texturedResultMesh; + } + delete textureGenerator; } - textureGenerator->generate(); - MeshLoader *texturedResultMesh = textureGenerator->takeResultMesh(); - if (nullptr != texturedResultMesh) { - m_previews[material.first] = new MeshLoader(*texturedResultMesh); - m_generatedMaterialIds.insert(material.first); - delete texturedResultMesh; - } - delete textureGenerator; } delete outcome; diff --git a/src/meshresultpostprocessor.cpp b/src/meshresultpostprocessor.cpp index 9e564425..496bb15b 100644 --- a/src/meshresultpostprocessor.cpp +++ b/src/meshresultpostprocessor.cpp @@ -23,6 +23,9 @@ Outcome *MeshResultPostProcessor::takePostProcessedOutcome() void MeshResultPostProcessor::poseProcess() { +#ifndef NDEBUG + return; +#endif if (!m_outcome->nodes.empty()) { { std::vector> triangleVertexUvs; diff --git a/src/meshwrapper.cpp b/src/meshwrapper.cpp index 7edf8c24..c7905d73 100644 --- a/src/meshwrapper.cpp +++ b/src/meshwrapper.cpp @@ -151,7 +151,7 @@ float MeshWrapper::angleOfBaseFaceAndPoint(size_t itemIndex, size_t vertexIndex) auto vd1 = calculateFaceVector(item.p1, item.p2, item.baseNormal); auto normal = QVector3D::normal(v2.position, v1.position, vp.position); auto vd2 = calculateFaceVector(item.p1, item.p2, normal); - return angleBetweenVectors(vd2, vd1); + return degreesBetweenVectors(vd2, vd1); } std::pair MeshWrapper::findBestVertexOnTheLeft(size_t itemIndex) diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp index 1f1111eb..4fff7b6e 100644 --- a/src/shortcuts.cpp +++ b/src/shortcuts.cpp @@ -15,6 +15,7 @@ void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget) defineKey(Qt::Key_Delete, &SkeletonGraphicsWidget::shortcutDelete); defineKey(Qt::Key_Backspace, &SkeletonGraphicsWidget::shortcutDelete); defineKey(Qt::Key_A, &SkeletonGraphicsWidget::shortcutAddMode); + defineKey(Qt::Key_G, &SkeletonGraphicsWidget::shortcutMarkMode); defineKey(Qt::CTRL + Qt::Key_A, &SkeletonGraphicsWidget::shortcutSelectAll); defineKey(Qt::CTRL + Qt::Key_Z, &SkeletonGraphicsWidget::shortcutUndo); defineKey(Qt::CTRL + Qt::SHIFT + Qt::Key_Z, &SkeletonGraphicsWidget::shortcutRedo); diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 1f709742..3d07ac71 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -337,6 +337,7 @@ enum class SkeletonDocumentEditMode { Add = 0, Select, + Mark, Paint, Drag, ZoomIn, diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 6fcb7acb..b22feaca 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "skeletongraphicswidget.h" #include "theme.h" #include "util.h" @@ -31,7 +32,9 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) m_lastAddedY(false), m_lastAddedZ(false), m_selectionItem(nullptr), + m_markerItem(nullptr), m_rangeSelectionStarted(false), + m_markerStarted(false), m_mouseEventFromSelf(false), m_moveHappened(false), m_lastRot(0), @@ -79,6 +82,10 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) m_selectionItem->hide(); scene()->addItem(m_selectionItem); + m_markerItem = new SkeletonGraphicsMarkerItem(); + m_markerItem->hide(); + scene()->addItem(m_markerItem); + m_mainOriginItem = new SkeletonGraphicsOriginItem(SkeletonProfile::Main); m_mainOriginItem->setRotated(m_rotated); m_mainOriginItem->hide(); @@ -725,6 +732,13 @@ void SkeletonGraphicsWidget::updateCursor() case SkeletonDocumentEditMode::Select: setCursor(QCursor(Theme::awesome()->icon(fa::mousepointer).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize), Theme::toolIconFontSize / 5, 0)); break; + case SkeletonDocumentEditMode::Mark: { + auto pixmap = Theme::awesome()->icon(fa::pencil).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize); + QPixmap replacedPixmap(pixmap.size()); + replacedPixmap.fill(m_markerItem->color()); + replacedPixmap.setMask(pixmap.createMaskFromColor(Qt::transparent)); + setCursor(QCursor(replacedPixmap, Theme::toolIconFontSize / 5, Theme::toolIconFontSize * 4 / 5)); + } break; case SkeletonDocumentEditMode::Paint: setCursor(QCursor(Theme::awesome()->icon(fa::paintbrush).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); break; @@ -857,6 +871,16 @@ bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event) } } + if (SkeletonDocumentEditMode::Mark == m_document->editMode) { + if (m_markerStarted) { + QPointF mouseScenePos = mouseEventScenePos(event); + m_markerItem->addPoint(mouseScenePos); + if (!m_markerItem->isVisible()) + m_markerItem->setVisible(true); + return true; + } + } + if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Add == m_document->editMode) { @@ -1410,7 +1434,7 @@ void SkeletonGraphicsWidget::rotateAllMainProfileCounterclockwise90DegreeAlongOr bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { - bool processed = m_dragStarted || m_moveStarted || m_rangeSelectionStarted; + bool processed = m_dragStarted || m_moveStarted || m_rangeSelectionStarted || m_markerStarted; if (m_dragStarted) { m_dragStarted = false; updateCursor(); @@ -1425,6 +1449,28 @@ bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event) m_selectionItem->hide(); m_rangeSelectionStarted = false; } + if (m_markerStarted) { + auto boundingBox = m_markerItem->polygon().boundingRect(); + if (boundingBox.width() * boundingBox.height() > 4) { + const QPolygonF &previousPolygon = m_markerItem->previousPolygon(); + if (previousPolygon.empty()) { + m_markerItem->save(); + } else { + if (m_markerItem->isMainProfile()) { + emit addPartByPolygons(m_markerItem->polygon(), previousPolygon, sceneRect().size()); + } else { + emit addPartByPolygons(previousPolygon, m_markerItem->polygon(), sceneRect().size()); + } + m_markerItem->reset(); + } + m_markerItem->hide(); + m_markerItem->toggleProfile(); + updateCursor(); + } else { + m_markerItem->clear(); + } + m_markerStarted = false; + } return processed; } return false; @@ -1567,6 +1613,11 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event) m_rangeSelectionStartPos = mouseEventScenePos(event); m_rangeSelectionStarted = true; } + } else if (SkeletonDocumentEditMode::Mark == m_document->editMode) { + if (!m_markerStarted) { + m_markerItem->addPoint(mouseEventScenePos(event)); + m_markerStarted = true; + } } } return false; @@ -1686,6 +1737,11 @@ void SkeletonGraphicsWidget::shortcutAddMode() } } +void SkeletonGraphicsWidget::shortcutMarkMode() +{ + emit setEditMode(SkeletonDocumentEditMode::Mark); +} + void SkeletonGraphicsWidget::shortcutUndo() { emit undo(); @@ -1777,7 +1833,8 @@ void SkeletonGraphicsWidget::shortcutZoomSelectedBy1() void SkeletonGraphicsWidget::shortcutRotateSelectedByMinus1() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + hasSelection()) { rotateSelected(-1); emit groupOperationAdded(); } @@ -1785,7 +1842,8 @@ void SkeletonGraphicsWidget::shortcutRotateSelectedByMinus1() void SkeletonGraphicsWidget::shortcutRotateSelectedBy1() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + hasSelection()) { rotateSelected(1); emit groupOperationAdded(); } @@ -1793,7 +1851,7 @@ void SkeletonGraphicsWidget::shortcutRotateSelectedBy1() void SkeletonGraphicsWidget::shortcutMoveSelectedToLeft() { - if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) { if (m_checkedOriginItem) { moveCheckedOrigin(-1, 0); emit groupOperationAdded(); @@ -1806,7 +1864,7 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToLeft() void SkeletonGraphicsWidget::shortcutMoveSelectedToRight() { - if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) { if (m_checkedOriginItem) { moveCheckedOrigin(1, 0); emit groupOperationAdded(); @@ -1819,7 +1877,7 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToRight() void SkeletonGraphicsWidget::shortcutMoveSelectedToUp() { - if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) { if (m_checkedOriginItem) { moveCheckedOrigin(0, -1); emit groupOperationAdded(); @@ -1832,7 +1890,7 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToUp() void SkeletonGraphicsWidget::shortcutMoveSelectedToDown() { - if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) { if (m_checkedOriginItem) { moveCheckedOrigin(0, 1); emit groupOperationAdded(); @@ -1845,7 +1903,8 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToDown() void SkeletonGraphicsWidget::shortcutScaleSelectedByMinus1() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + hasSelection()) { scaleSelected(-1); emit groupOperationAdded(); } @@ -1853,7 +1912,8 @@ void SkeletonGraphicsWidget::shortcutScaleSelectedByMinus1() void SkeletonGraphicsWidget::shortcutScaleSelectedBy1() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + hasSelection()) { scaleSelected(1); emit groupOperationAdded(); } @@ -1861,7 +1921,8 @@ void SkeletonGraphicsWidget::shortcutScaleSelectedBy1() void SkeletonGraphicsWidget::shortcutSwitchProfileOnSelected() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + hasSelection()) { switchProfileOnRangeSelection(); } } @@ -1882,7 +1943,8 @@ void SkeletonGraphicsWidget::shortcutShowOrHideSelectedPart() void SkeletonGraphicsWidget::shortcutEnableOrDisableSelectedPart() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + !m_lastCheckedPart.isNull()) { const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); bool partDisabled = part && part->disabled; emit setPartDisableState(m_lastCheckedPart, !partDisabled); @@ -1892,7 +1954,8 @@ void SkeletonGraphicsWidget::shortcutEnableOrDisableSelectedPart() void SkeletonGraphicsWidget::shortcutLockOrUnlockSelectedPart() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + !m_lastCheckedPart.isNull()) { const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); bool partLocked = part && part->locked; emit setPartLockState(m_lastCheckedPart, !partLocked); @@ -1902,7 +1965,8 @@ void SkeletonGraphicsWidget::shortcutLockOrUnlockSelectedPart() void SkeletonGraphicsWidget::shortcutXmirrorOnOrOffSelectedPart() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + !m_lastCheckedPart.isNull()) { const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); bool partXmirrored = part && part->xMirrored; emit setPartXmirrorState(m_lastCheckedPart, !partXmirrored); @@ -1912,7 +1976,8 @@ void SkeletonGraphicsWidget::shortcutXmirrorOnOrOffSelectedPart() void SkeletonGraphicsWidget::shortcutSubdivedOrNotSelectedPart() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + !m_lastCheckedPart.isNull()) { const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); bool partSubdived = part && part->subdived; emit setPartSubdivState(m_lastCheckedPart, !partSubdived); @@ -1922,7 +1987,8 @@ void SkeletonGraphicsWidget::shortcutSubdivedOrNotSelectedPart() void SkeletonGraphicsWidget::shortcutChamferedOrNotSelectedPart() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + !m_lastCheckedPart.isNull()) { const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); bool partChamfered = part && part->chamfered; emit setPartChamferState(m_lastCheckedPart, !partChamfered); @@ -1937,7 +2003,8 @@ void SkeletonGraphicsWidget::shortcutSelectAll() void SkeletonGraphicsWidget::shortcutRoundEndOrNotSelectedPart() { - if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { + if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) && + !m_lastCheckedPart.isNull()) { const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); bool partRounded = part && part->rounded; emit setPartRoundState(m_lastCheckedPart, !partRounded); diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 9cd9fc3b..0c6c62e3 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -124,6 +124,68 @@ public: } }; +class SkeletonGraphicsMarkerItem : public QGraphicsPolygonItem +{ +public: + SkeletonGraphicsMarkerItem() + { + updateAppearance(); + } + void addPoint(const QPointF &point) + { + m_polygon.append(point); + setPolygon(m_polygon); + } + void clear() + { + m_polygon.clear(); + setPolygon(m_polygon); + } + QColor color() + { + return m_mainProfile ? Theme::red : Theme::green; + } + bool isMainProfile() + { + return m_mainProfile; + } + void toggleProfile() + { + m_mainProfile = !m_mainProfile; + updateAppearance(); + } + void save() + { + m_previousPolygon = m_polygon; + clear(); + } + const QPolygonF &previousPolygon() + { + return m_previousPolygon; + } + const QPolygonF &polygon() + { + return m_polygon; + } + void reset() + { + m_previousPolygon.clear(); + clear(); + } +private: + QPolygonF m_polygon; + QPolygonF m_previousPolygon; + bool m_mainProfile = true; + void updateAppearance() + { + QColor penColor(color()); + QPen pen(penColor); + pen.setWidth(2); + pen.setStyle(Qt::SolidLine); + setPen(pen); + } +}; + class SkeletonGraphicsNodeItem : public QGraphicsEllipseItem { public: @@ -441,6 +503,7 @@ signals: void shortcutToggleFlatShading(); void shortcutToggleRotation(); void createGriddedPartsFromNodes(const std::set &nodeIds); + void addPartByPolygons(const QPolygonF &mainProfile, const QPolygonF &sideProfile, const QSizeF &canvasSize); public: SkeletonGraphicsWidget(const SkeletonDocument *document); std::map> nodeItemMap; @@ -544,6 +607,7 @@ public slots: void createWrapParts(); void shortcutDelete(); void shortcutAddMode(); + void shortcutMarkMode(); void shortcutUndo(); void shortcutRedo(); void shortcutXlock(); @@ -618,7 +682,9 @@ private: //need initalize float m_lastAddedY; float m_lastAddedZ; SkeletonGraphicsSelectionItem *m_selectionItem; + SkeletonGraphicsMarkerItem *m_markerItem; bool m_rangeSelectionStarted; + bool m_markerStarted; bool m_mouseEventFromSelf; bool m_moveHappened; int m_lastRot; diff --git a/src/strokemodifier.cpp b/src/strokemodifier.cpp index a69aeb00..8d487231 100644 --- a/src/strokemodifier.cpp +++ b/src/strokemodifier.cpp @@ -155,8 +155,10 @@ void StrokeModifier::finalize() size_t newInsertNum = currentEdgeLength / targetEdgeLength; if (newInsertNum < 1) newInsertNum = 1; - if (newInsertNum > 100) + if (newInsertNum > 100) { + addEdge(edge.firstNodeIndex, edge.secondNodeIndex); continue; + } float stepFactor = 1.0 / (newInsertNum + 1); std::vector nodeIndices; nodeIndices.push_back(edge.firstNodeIndex); diff --git a/src/util.cpp b/src/util.cpp index 8fc3b42b..382320a3 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -82,7 +82,7 @@ float radianBetweenVectors(const QVector3D &first, const QVector3D &second) return std::acos(QVector3D::dotProduct(first.normalized(), second.normalized())); }; -float angleBetweenVectors(const QVector3D &first, const QVector3D &second) +float degreesBetweenVectors(const QVector3D &first, const QVector3D &second) { return radianBetweenVectors(first, second) * 180.0 / M_PI; } @@ -160,9 +160,9 @@ void angleSmooth(const std::vector &vertices, const auto &v2 = vertices[sourceTriangle[1]]; const auto &v3 = vertices[sourceTriangle[2]]; float area = areaOfTriangle(v1, v2, v3); - float angles[] = {angleBetweenVectors(v2-v1, v3-v1), - angleBetweenVectors(v1-v2, v3-v2), - angleBetweenVectors(v1-v3, v2-v3)}; + float angles[] = {degreesBetweenVectors(v2-v1, v3-v1), + degreesBetweenVectors(v1-v2, v3-v2), + degreesBetweenVectors(v1-v3, v2-v3)}; for (int i = 0; i < 3; ++i) { if (sourceTriangle[i] >= vertices.size()) { qDebug() << "Invalid vertex index" << sourceTriangle[i] << "vertices size" << vertices.size(); @@ -183,7 +183,7 @@ void angleSmooth(const std::vector &vertices, float degrees = 0; auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first}); if (findDegreesResult == degreesBetweenFacesMap.end()) { - degrees = angleBetweenVectors(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]); + degrees = degreesBetweenVectors(triangleNormals[triangleVertex.first], triangleNormals[otherTriangleVertex.first]); degreesBetweenFacesMap.insert({{triangleVertex.first, otherTriangleVertex.first}, degrees}); degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees}); } else { diff --git a/src/util.h b/src/util.h index 36c98fed..00e0e4ac 100644 --- a/src/util.h +++ b/src/util.h @@ -23,7 +23,7 @@ QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal); QString unifiedWindowTitle(const QString &text); QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t); float radianBetweenVectors(const QVector3D &first, const QVector3D &second); -float angleBetweenVectors(const QVector3D &first, const QVector3D &second); +float degreesBetweenVectors(const QVector3D &first, const QVector3D &second); float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c); QQuaternion eulerAnglesToQuaternion(double pitch, double yaw, double roll); void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, double *roll);