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:
创建包裹部件
+
+
+ 马克笔
+
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);