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 generatedmaster
parent
7a5065865a
commit
9f55f738f3
|
@ -1218,4 +1218,10 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
|
|||
<h1>Coons patch</h1>
|
||||
<pre>
|
||||
https://en.wikipedia.org/wiki/Coons_patch
|
||||
</pre>
|
||||
|
||||
<h1>T. Y. ZHANG and C. Y. SUEN</h1>
|
||||
<pre>
|
||||
A Fast Parallel Algorithm for Thinning Digital Patterns
|
||||
https://dl.acm.org/citation.cfm?id=358023
|
||||
</pre>
|
|
@ -34,6 +34,8 @@ Keyboard
|
|||
+----------------------+--------------------------------------------------------------------------+
|
||||
| D | Enter Paint Mode |
|
||||
+----------------------+--------------------------------------------------------------------------+
|
||||
| G | Enter Mark Mode |
|
||||
+----------------------+--------------------------------------------------------------------------+
|
||||
| CTRL + S | Save |
|
||||
+----------------------+--------------------------------------------------------------------------+
|
||||
| CTRL + Z | Undo |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -386,6 +386,10 @@ Tips:
|
|||
<source>Create Wrap Parts</source>
|
||||
<translation>创建包裹部件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Marker pen</source>
|
||||
<translation>马克笔</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ExportPreviewWidget</name>
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <cmath>
|
||||
#include <QUuid>
|
||||
#include <cmath>
|
||||
#include <QTransform>
|
||||
#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<std::pair<QVector2D, float>> *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<std::pair<int, int>> imageSkeleton;
|
||||
int imageArea = imageSkeletonExtractor.getArea();
|
||||
imageSkeletonExtractor.getSkeleton(&imageSkeleton);
|
||||
const std::set<std::pair<int, int>> &blackPixels = imageSkeletonExtractor.getBlackPixels();
|
||||
|
||||
std::vector<std::pair<int, int>> 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<QVector3D> selectedPositions;
|
||||
selectedPositions.reserve(selectedNodes.size());
|
||||
for (const auto &it: selectedNodes)
|
||||
selectedPositions.push_back(QVector3D(it.first, it.second, 0.0f));
|
||||
|
||||
std::vector<QVector3D> 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<int> 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<std::pair<int, int>> &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<std::pair<QVector2D, float>> *skeleton)
|
||||
{
|
||||
if (skeleton->empty())
|
||||
return;
|
||||
|
||||
std::vector<float> 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<std::pair<QVector2D, float>> 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<QString, QString> 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<QString, QString> 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<QString> nodeIdStrings;
|
||||
nodeIdStrings.reserve(m_nodes.size());
|
||||
for (const auto &it: m_nodes) {
|
||||
auto nodeId = QUuid::createUuid();
|
||||
auto nodeIdString = nodeId.toString();
|
||||
std::map<QString, QString> 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<QString, QString> snapshotEdge;
|
||||
snapshotEdge["id"] = edgeIdString;
|
||||
snapshotEdge["from"] = nodeIdStrings[h];
|
||||
snapshotEdge["to"] = nodeIdStrings[i];
|
||||
snapshotEdge["partId"] = partIdString;
|
||||
m_snapshot.edges[edgeIdString] = snapshotEdge;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef DUST3D_CONTOUR_TO_PART_CONVERTER_H
|
||||
#define DUST3D_CONTOUR_TO_PART_CONVERTER_H
|
||||
#include <QObject>
|
||||
#include <QPolygonF>
|
||||
#include <QVector3D>
|
||||
#include <QVector2D>
|
||||
#include <set>
|
||||
#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<std::pair<QVector3D, float>> m_nodes;
|
||||
void convert();
|
||||
void extractSkeleton(const QPolygonF &polygon,
|
||||
std::vector<std::pair<QVector2D, float>> *skeleton);
|
||||
int calculateNodeRadius(const QVector3D &node,
|
||||
const QVector3D &direction,
|
||||
const std::set<std::pair<int, int>> &black);
|
||||
void nodesToSnapshot();
|
||||
void smoothRadius(std::vector<std::pair<QVector2D, float>> *skeleton);
|
||||
void optimizeNodes();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -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"));
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QPolygon>
|
||||
#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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -61,7 +61,7 @@ void GridMeshBuilder::splitCycleToPolylines(const std::vector<size_t> &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);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
#include <QDebug>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include "imageskeletonextractor.h"
|
||||
|
||||
// This is an implementation of the following paper:
|
||||
// <A Fast Parallel Algorithm for Thinning Digital Patterns>
|
||||
// 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<std::pair<int, int>> &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<std::pair<int, int>> 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<std::pair<int, int>> *skeleton)
|
||||
{
|
||||
if (nullptr == m_grayscaleImage)
|
||||
return;
|
||||
|
||||
std::map<std::pair<int, int>, std::vector<std::pair<int, int>>> 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<int, int> &branch, const std::pair<int, int> &start) {
|
||||
std::set<std::pair<int, int>> visited;
|
||||
visited.insert(branch);
|
||||
std::queue<std::pair<int, int>> 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<std::pair<std::pair<int, int>, 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<std::pair<int, int>, size_t> &first,
|
||||
const std::pair<std::pair<int, int>, size_t> &second) {
|
||||
return first.second < second.second;
|
||||
});
|
||||
it.second = std::vector<std::pair<int, int>> {routes[routes.size() - 2].first,
|
||||
routes[routes.size() - 1].first};
|
||||
}
|
||||
}
|
||||
|
||||
std::queue<std::pair<int, int>> waitPoints;
|
||||
for (const auto &it: links) {
|
||||
if (1 == it.second.size()) {
|
||||
waitPoints.push(it.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::set<std::pair<int, int>> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
class ImageSkeletonExtractor : QObject
|
||||
{
|
||||
|
@ -33,10 +34,14 @@ public:
|
|||
void setImage(QImage *image);
|
||||
void extract();
|
||||
QImage *takeResultGrayscaleImage();
|
||||
void getSkeleton(std::vector<std::pair<int, int>> *skeleton);
|
||||
int getArea();
|
||||
const std::set<std::pair<int, int>> &getBlackPixels();
|
||||
private:
|
||||
QImage *m_image = nullptr;
|
||||
QImage *m_grayscaleImage = nullptr;
|
||||
static const int m_targetHeight;
|
||||
int m_area = 0;
|
||||
std::set<std::pair<int, int>> 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -23,6 +23,9 @@ Outcome *MeshResultPostProcessor::takePostProcessedOutcome()
|
|||
|
||||
void MeshResultPostProcessor::poseProcess()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
return;
|
||||
#endif
|
||||
if (!m_outcome->nodes.empty()) {
|
||||
{
|
||||
std::vector<std::vector<QVector2D>> triangleVertexUvs;
|
||||
|
|
|
@ -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<size_t, bool> MeshWrapper::findBestVertexOnTheLeft(size_t itemIndex)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -337,6 +337,7 @@ enum class SkeletonDocumentEditMode
|
|||
{
|
||||
Add = 0,
|
||||
Select,
|
||||
Mark,
|
||||
Paint,
|
||||
Drag,
|
||||
ZoomIn,
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <QApplication>
|
||||
#include <QMatrix4x4>
|
||||
#include <queue>
|
||||
#include <QBitmap>
|
||||
#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);
|
||||
|
|
|
@ -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<QUuid> &nodeIds);
|
||||
void addPartByPolygons(const QPolygonF &mainProfile, const QPolygonF &sideProfile, const QSizeF &canvasSize);
|
||||
public:
|
||||
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
||||
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> 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;
|
||||
|
|
|
@ -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<size_t> nodeIndices;
|
||||
nodeIndices.push_back(edge.firstNodeIndex);
|
||||
|
|
10
src/util.cpp
10
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<QVector3D> &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<QVector3D> &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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue