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>
|
<h1>Coons patch</h1>
|
||||||
<pre>
|
<pre>
|
||||||
https://en.wikipedia.org/wiki/Coons_patch
|
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>
|
</pre>
|
|
@ -34,6 +34,8 @@ Keyboard
|
||||||
+----------------------+--------------------------------------------------------------------------+
|
+----------------------+--------------------------------------------------------------------------+
|
||||||
| D | Enter Paint Mode |
|
| D | Enter Paint Mode |
|
||||||
+----------------------+--------------------------------------------------------------------------+
|
+----------------------+--------------------------------------------------------------------------+
|
||||||
|
| G | Enter Mark Mode |
|
||||||
|
+----------------------+--------------------------------------------------------------------------+
|
||||||
| CTRL + S | Save |
|
| CTRL + S | Save |
|
||||||
+----------------------+--------------------------------------------------------------------------+
|
+----------------------+--------------------------------------------------------------------------+
|
||||||
| CTRL + Z | Undo |
|
| CTRL + Z | Undo |
|
||||||
|
|
|
@ -480,6 +480,9 @@ HEADERS += src/booleanmesh.h
|
||||||
SOURCES += src/imageskeletonextractor.cpp
|
SOURCES += src/imageskeletonextractor.cpp
|
||||||
HEADERS += src/imageskeletonextractor.h
|
HEADERS += src/imageskeletonextractor.h
|
||||||
|
|
||||||
|
SOURCES += src/contourtopartconverter.cpp
|
||||||
|
HEADERS += src/contourtopartconverter.h
|
||||||
|
|
||||||
SOURCES += src/main.cpp
|
SOURCES += src/main.cpp
|
||||||
|
|
||||||
HEADERS += src/version.h
|
HEADERS += src/version.h
|
||||||
|
|
|
@ -386,6 +386,10 @@ Tips:
|
||||||
<source>Create Wrap Parts</source>
|
<source>Create Wrap Parts</source>
|
||||||
<translation>创建包裹部件</translation>
|
<translation>创建包裹部件</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Marker pen</source>
|
||||||
|
<translation>马克笔</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>ExportPreviewWidget</name>
|
<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 "scriptrunner.h"
|
||||||
#include "mousepicker.h"
|
#include "mousepicker.h"
|
||||||
#include "imageforever.h"
|
#include "imageforever.h"
|
||||||
|
#include "contourtopartconverter.h"
|
||||||
|
|
||||||
unsigned long Document::m_maxSnapshot = 1000;
|
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);
|
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)
|
void Document::addNodeWithId(QUuid nodeId, float x, float y, float z, float radius, QUuid fromNodeId)
|
||||||
{
|
{
|
||||||
createNode(nodeId, x, y, z, radius, fromNodeId);
|
createNode(nodeId, x, y, z, radius, fromNodeId);
|
||||||
|
@ -1522,7 +1545,12 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
||||||
part.id = newUuid;
|
part.id = newUuid;
|
||||||
oldNewIdMap[QUuid(partKv.first)] = part.id;
|
oldNewIdMap[QUuid(partKv.first)] = part.id;
|
||||||
part.name = valueOfKeyInMapOrEmpty(partKv.second, "name");
|
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.locked = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "locked"));
|
||||||
part.subdived = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "subdived"));
|
part.subdived = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "subdived"));
|
||||||
part.disabled = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "disabled"));
|
part.disabled = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "disabled"));
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
|
#include <QPolygon>
|
||||||
#include "snapshot.h"
|
#include "snapshot.h"
|
||||||
#include "meshloader.h"
|
#include "meshloader.h"
|
||||||
#include "meshgenerator.h"
|
#include "meshgenerator.h"
|
||||||
|
@ -567,6 +568,7 @@ public slots:
|
||||||
void removeNode(QUuid nodeId);
|
void removeNode(QUuid nodeId);
|
||||||
void removeEdge(QUuid edgeId);
|
void removeEdge(QUuid edgeId);
|
||||||
void removePart(QUuid partId);
|
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 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 addNode(float x, float y, float z, float radius, QUuid fromNodeId);
|
||||||
void scaleNodeByAddRadius(QUuid nodeId, float amount);
|
void scaleNodeByAddRadius(QUuid nodeId, float amount);
|
||||||
|
|
|
@ -160,6 +160,10 @@ DocumentWindow::DocumentWindow() :
|
||||||
selectButton->setToolTip(tr("Select node on canvas"));
|
selectButton->setToolTip(tr("Select node on canvas"));
|
||||||
Theme::initAwesomeButton(selectButton);
|
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));
|
QPushButton *paintButton = new QPushButton(QChar(fa::paintbrush));
|
||||||
paintButton->setToolTip(tr("Paint brush"));
|
paintButton->setToolTip(tr("Paint brush"));
|
||||||
Theme::initAwesomeButton(paintButton);
|
Theme::initAwesomeButton(paintButton);
|
||||||
|
@ -240,6 +244,7 @@ DocumentWindow::DocumentWindow() :
|
||||||
|
|
||||||
toolButtonLayout->addWidget(addButton);
|
toolButtonLayout->addWidget(addButton);
|
||||||
toolButtonLayout->addWidget(selectButton);
|
toolButtonLayout->addWidget(selectButton);
|
||||||
|
toolButtonLayout->addWidget(markerButton);
|
||||||
toolButtonLayout->addWidget(paintButton);
|
toolButtonLayout->addWidget(paintButton);
|
||||||
//toolButtonLayout->addWidget(dragButton);
|
//toolButtonLayout->addWidget(dragButton);
|
||||||
toolButtonLayout->addWidget(zoomInButton);
|
toolButtonLayout->addWidget(zoomInButton);
|
||||||
|
@ -853,6 +858,10 @@ DocumentWindow::DocumentWindow() :
|
||||||
m_document->setEditMode(SkeletonDocumentEditMode::Select);
|
m_document->setEditMode(SkeletonDocumentEditMode::Select);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(markerButton, &QPushButton::clicked, [=]() {
|
||||||
|
m_document->setEditMode(SkeletonDocumentEditMode::Mark);
|
||||||
|
});
|
||||||
|
|
||||||
connect(paintButton, &QPushButton::clicked, [=]() {
|
connect(paintButton, &QPushButton::clicked, [=]() {
|
||||||
m_document->setEditMode(SkeletonDocumentEditMode::Paint);
|
m_document->setEditMode(SkeletonDocumentEditMode::Paint);
|
||||||
});
|
});
|
||||||
|
@ -942,6 +951,7 @@ DocumentWindow::DocumentWindow() :
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartCutRotation);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartCutRotation);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::createGriddedPartsFromNodes, m_document, &Document::createGriddedPartsFromNodes);
|
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::setXlockState, m_document, &Document::setXlockState);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &Document::setYlockState);
|
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();
|
size_t j = (i + 1) % cycle.size();
|
||||||
QVector3D hi = m_nodeVertices[cycle[i]].position - m_nodeVertices[cycle[h]].position;
|
QVector3D hi = m_nodeVertices[cycle[i]].position - m_nodeVertices[cycle[h]].position;
|
||||||
QVector3D ij = m_nodeVertices[cycle[j]].position - m_nodeVertices[cycle[i]].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;
|
//qDebug() << "angle[" << i << "]:" << angle;
|
||||||
if (angle >= m_polylineAngleChangeThreshold)
|
if (angle >= m_polylineAngleChangeThreshold)
|
||||||
cornerIndices.push_back(i);
|
cornerIndices.push_back(i);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
#include <QDebug>
|
||||||
|
#include <queue>
|
||||||
|
#include <set>
|
||||||
#include "imageskeletonextractor.h"
|
#include "imageskeletonextractor.h"
|
||||||
|
|
||||||
// This is an implementation of the following paper:
|
// This is an implementation of the following paper:
|
||||||
// <A Fast Parallel Algorithm for Thinning Digital Patterns>
|
// <A Fast Parallel Algorithm for Thinning Digital Patterns>
|
||||||
// T. Y. ZHANG and C. Y. SUEN
|
// T. Y. ZHANG and C. Y. SUEN
|
||||||
|
|
||||||
const int ImageSkeletonExtractor::m_targetHeight = 256;
|
|
||||||
|
|
||||||
ImageSkeletonExtractor::~ImageSkeletonExtractor()
|
ImageSkeletonExtractor::~ImageSkeletonExtractor()
|
||||||
{
|
{
|
||||||
delete m_image;
|
delete m_image;
|
||||||
|
@ -71,12 +72,34 @@ bool ImageSkeletonExtractor::secondSubiterationSatisfied(int i, int j)
|
||||||
return true;
|
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()
|
void ImageSkeletonExtractor::extract()
|
||||||
{
|
{
|
||||||
m_grayscaleImage = new QImage(m_image->convertToFormat(
|
m_grayscaleImage = new QImage(m_image->convertToFormat(QImage::Format_Grayscale8));
|
||||||
QImage::Format_Grayscale8).scaled(
|
calculateAreaAndBlackPixels();
|
||||||
QSize(m_targetHeight, m_targetHeight), Qt::KeepAspectRatio));
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
std::vector<std::pair<int, int>> firstSatisfied;
|
std::vector<std::pair<int, int>> firstSatisfied;
|
||||||
for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) {
|
for (int i = 1; i < (int)m_grayscaleImage->width() - 1; ++i) {
|
||||||
|
@ -98,7 +121,140 @@ void ImageSkeletonExtractor::extract()
|
||||||
setWhite(it.first, it.second);
|
setWhite(it.first, it.second);
|
||||||
if (firstSatisfied.empty() && secondSatisfied.empty())
|
if (firstSatisfied.empty() && secondSatisfied.empty())
|
||||||
break;
|
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 <QImage>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
class ImageSkeletonExtractor : QObject
|
class ImageSkeletonExtractor : QObject
|
||||||
{
|
{
|
||||||
|
@ -33,10 +34,14 @@ public:
|
||||||
void setImage(QImage *image);
|
void setImage(QImage *image);
|
||||||
void extract();
|
void extract();
|
||||||
QImage *takeResultGrayscaleImage();
|
QImage *takeResultGrayscaleImage();
|
||||||
|
void getSkeleton(std::vector<std::pair<int, int>> *skeleton);
|
||||||
|
int getArea();
|
||||||
|
const std::set<std::pair<int, int>> &getBlackPixels();
|
||||||
private:
|
private:
|
||||||
QImage *m_image = nullptr;
|
QImage *m_image = nullptr;
|
||||||
QImage *m_grayscaleImage = 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)
|
bool isBlack(int i, int j)
|
||||||
{
|
{
|
||||||
|
@ -78,6 +83,7 @@ private:
|
||||||
|
|
||||||
bool firstSubiterationSatisfied(int i, int j);
|
bool firstSubiterationSatisfied(int i, int j);
|
||||||
bool secondSubiterationSatisfied(int i, int j);
|
bool secondSubiterationSatisfied(int i, int j);
|
||||||
|
void calculateAreaAndBlackPixels();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -19,10 +19,10 @@ int main(int argc, char ** argv)
|
||||||
if (translator.load(QLocale(), QLatin1String("dust3d"), QLatin1String("_"), QLatin1String(":/languages")))
|
if (translator.load(QLocale(), QLatin1String("dust3d"), QLatin1String("_"), QLatin1String(":/languages")))
|
||||||
app.installTranslator(&translator);
|
app.installTranslator(&translator);
|
||||||
|
|
||||||
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
|
//QSurfaceFormat format = QSurfaceFormat::defaultFormat();
|
||||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
//format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||||
format.setVersion(3, 3);
|
//format.setVersion(3, 3);
|
||||||
QSurfaceFormat::setDefaultFormat(format);
|
//QSurfaceFormat::setDefaultFormat(format);
|
||||||
|
|
||||||
// QuantumCD/Qt 5 Dark Fusion Palette
|
// QuantumCD/Qt 5 Dark Fusion Palette
|
||||||
// https://gist.github.com/QuantumCD/6245215
|
// https://gist.github.com/QuantumCD/6245215
|
||||||
|
|
|
@ -73,35 +73,37 @@ void MaterialPreviewsGenerator::generate()
|
||||||
delete poseProcessor;
|
delete poseProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &material: m_materials) {
|
if (nullptr != outcome) {
|
||||||
TextureGenerator *textureGenerator = new TextureGenerator(*outcome);
|
for (const auto &material: m_materials) {
|
||||||
for (const auto &layer: material.second) {
|
TextureGenerator *textureGenerator = new TextureGenerator(*outcome);
|
||||||
for (const auto &mapItem: layer.maps) {
|
for (const auto &layer: material.second) {
|
||||||
const QImage *image = ImageForever::get(mapItem.imageId);
|
for (const auto &mapItem: layer.maps) {
|
||||||
if (nullptr == image)
|
const QImage *image = ImageForever::get(mapItem.imageId);
|
||||||
continue;
|
if (nullptr == image)
|
||||||
for (const auto &partId: partIds) {
|
continue;
|
||||||
if (TextureType::BaseColor == mapItem.forWhat)
|
for (const auto &partId: partIds) {
|
||||||
textureGenerator->addPartColorMap(partId, image, layer.tileScale);
|
if (TextureType::BaseColor == mapItem.forWhat)
|
||||||
else if (TextureType::Normal == mapItem.forWhat)
|
textureGenerator->addPartColorMap(partId, image, layer.tileScale);
|
||||||
textureGenerator->addPartNormalMap(partId, image, layer.tileScale);
|
else if (TextureType::Normal == mapItem.forWhat)
|
||||||
else if (TextureType::Metalness == mapItem.forWhat)
|
textureGenerator->addPartNormalMap(partId, image, layer.tileScale);
|
||||||
textureGenerator->addPartMetalnessMap(partId, image, layer.tileScale);
|
else if (TextureType::Metalness == mapItem.forWhat)
|
||||||
else if (TextureType::Roughness == mapItem.forWhat)
|
textureGenerator->addPartMetalnessMap(partId, image, layer.tileScale);
|
||||||
textureGenerator->addPartRoughnessMap(partId, image, layer.tileScale);
|
else if (TextureType::Roughness == mapItem.forWhat)
|
||||||
else if (TextureType::AmbientOcclusion == mapItem.forWhat)
|
textureGenerator->addPartRoughnessMap(partId, image, layer.tileScale);
|
||||||
textureGenerator->addPartAmbientOcclusionMap(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;
|
delete outcome;
|
||||||
|
|
|
@ -23,6 +23,9 @@ Outcome *MeshResultPostProcessor::takePostProcessedOutcome()
|
||||||
|
|
||||||
void MeshResultPostProcessor::poseProcess()
|
void MeshResultPostProcessor::poseProcess()
|
||||||
{
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
if (!m_outcome->nodes.empty()) {
|
if (!m_outcome->nodes.empty()) {
|
||||||
{
|
{
|
||||||
std::vector<std::vector<QVector2D>> triangleVertexUvs;
|
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 vd1 = calculateFaceVector(item.p1, item.p2, item.baseNormal);
|
||||||
auto normal = QVector3D::normal(v2.position, v1.position, vp.position);
|
auto normal = QVector3D::normal(v2.position, v1.position, vp.position);
|
||||||
auto vd2 = calculateFaceVector(item.p1, item.p2, normal);
|
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)
|
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_Delete, &SkeletonGraphicsWidget::shortcutDelete);
|
||||||
defineKey(Qt::Key_Backspace, &SkeletonGraphicsWidget::shortcutDelete);
|
defineKey(Qt::Key_Backspace, &SkeletonGraphicsWidget::shortcutDelete);
|
||||||
defineKey(Qt::Key_A, &SkeletonGraphicsWidget::shortcutAddMode);
|
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_A, &SkeletonGraphicsWidget::shortcutSelectAll);
|
||||||
defineKey(Qt::CTRL + Qt::Key_Z, &SkeletonGraphicsWidget::shortcutUndo);
|
defineKey(Qt::CTRL + Qt::Key_Z, &SkeletonGraphicsWidget::shortcutUndo);
|
||||||
defineKey(Qt::CTRL + Qt::SHIFT + Qt::Key_Z, &SkeletonGraphicsWidget::shortcutRedo);
|
defineKey(Qt::CTRL + Qt::SHIFT + Qt::Key_Z, &SkeletonGraphicsWidget::shortcutRedo);
|
||||||
|
|
|
@ -337,6 +337,7 @@ enum class SkeletonDocumentEditMode
|
||||||
{
|
{
|
||||||
Add = 0,
|
Add = 0,
|
||||||
Select,
|
Select,
|
||||||
|
Mark,
|
||||||
Paint,
|
Paint,
|
||||||
Drag,
|
Drag,
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QMatrix4x4>
|
#include <QMatrix4x4>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <QBitmap>
|
||||||
#include "skeletongraphicswidget.h"
|
#include "skeletongraphicswidget.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
@ -31,7 +32,9 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
|
||||||
m_lastAddedY(false),
|
m_lastAddedY(false),
|
||||||
m_lastAddedZ(false),
|
m_lastAddedZ(false),
|
||||||
m_selectionItem(nullptr),
|
m_selectionItem(nullptr),
|
||||||
|
m_markerItem(nullptr),
|
||||||
m_rangeSelectionStarted(false),
|
m_rangeSelectionStarted(false),
|
||||||
|
m_markerStarted(false),
|
||||||
m_mouseEventFromSelf(false),
|
m_mouseEventFromSelf(false),
|
||||||
m_moveHappened(false),
|
m_moveHappened(false),
|
||||||
m_lastRot(0),
|
m_lastRot(0),
|
||||||
|
@ -79,6 +82,10 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
|
||||||
m_selectionItem->hide();
|
m_selectionItem->hide();
|
||||||
scene()->addItem(m_selectionItem);
|
scene()->addItem(m_selectionItem);
|
||||||
|
|
||||||
|
m_markerItem = new SkeletonGraphicsMarkerItem();
|
||||||
|
m_markerItem->hide();
|
||||||
|
scene()->addItem(m_markerItem);
|
||||||
|
|
||||||
m_mainOriginItem = new SkeletonGraphicsOriginItem(SkeletonProfile::Main);
|
m_mainOriginItem = new SkeletonGraphicsOriginItem(SkeletonProfile::Main);
|
||||||
m_mainOriginItem->setRotated(m_rotated);
|
m_mainOriginItem->setRotated(m_rotated);
|
||||||
m_mainOriginItem->hide();
|
m_mainOriginItem->hide();
|
||||||
|
@ -725,6 +732,13 @@ void SkeletonGraphicsWidget::updateCursor()
|
||||||
case SkeletonDocumentEditMode::Select:
|
case SkeletonDocumentEditMode::Select:
|
||||||
setCursor(QCursor(Theme::awesome()->icon(fa::mousepointer).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize), Theme::toolIconFontSize / 5, 0));
|
setCursor(QCursor(Theme::awesome()->icon(fa::mousepointer).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize), Theme::toolIconFontSize / 5, 0));
|
||||||
break;
|
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:
|
case SkeletonDocumentEditMode::Paint:
|
||||||
setCursor(QCursor(Theme::awesome()->icon(fa::paintbrush).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
|
setCursor(QCursor(Theme::awesome()->icon(fa::paintbrush).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
|
||||||
break;
|
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 ||
|
if (SkeletonDocumentEditMode::Select == m_document->editMode ||
|
||||||
SkeletonDocumentEditMode::Add == m_document->editMode) {
|
SkeletonDocumentEditMode::Add == m_document->editMode) {
|
||||||
|
|
||||||
|
@ -1410,7 +1434,7 @@ void SkeletonGraphicsWidget::rotateAllMainProfileCounterclockwise90DegreeAlongOr
|
||||||
bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event)
|
bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (event->button() == Qt::LeftButton) {
|
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) {
|
if (m_dragStarted) {
|
||||||
m_dragStarted = false;
|
m_dragStarted = false;
|
||||||
updateCursor();
|
updateCursor();
|
||||||
|
@ -1425,6 +1449,28 @@ bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event)
|
||||||
m_selectionItem->hide();
|
m_selectionItem->hide();
|
||||||
m_rangeSelectionStarted = false;
|
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 processed;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1567,6 +1613,11 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event)
|
||||||
m_rangeSelectionStartPos = mouseEventScenePos(event);
|
m_rangeSelectionStartPos = mouseEventScenePos(event);
|
||||||
m_rangeSelectionStarted = true;
|
m_rangeSelectionStarted = true;
|
||||||
}
|
}
|
||||||
|
} else if (SkeletonDocumentEditMode::Mark == m_document->editMode) {
|
||||||
|
if (!m_markerStarted) {
|
||||||
|
m_markerItem->addPoint(mouseEventScenePos(event));
|
||||||
|
m_markerStarted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -1686,6 +1737,11 @@ void SkeletonGraphicsWidget::shortcutAddMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SkeletonGraphicsWidget::shortcutMarkMode()
|
||||||
|
{
|
||||||
|
emit setEditMode(SkeletonDocumentEditMode::Mark);
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutUndo()
|
void SkeletonGraphicsWidget::shortcutUndo()
|
||||||
{
|
{
|
||||||
emit undo();
|
emit undo();
|
||||||
|
@ -1777,7 +1833,8 @@ void SkeletonGraphicsWidget::shortcutZoomSelectedBy1()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutRotateSelectedByMinus1()
|
void SkeletonGraphicsWidget::shortcutRotateSelectedByMinus1()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) {
|
if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) &&
|
||||||
|
hasSelection()) {
|
||||||
rotateSelected(-1);
|
rotateSelected(-1);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
}
|
}
|
||||||
|
@ -1785,7 +1842,8 @@ void SkeletonGraphicsWidget::shortcutRotateSelectedByMinus1()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutRotateSelectedBy1()
|
void SkeletonGraphicsWidget::shortcutRotateSelectedBy1()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) {
|
if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) &&
|
||||||
|
hasSelection()) {
|
||||||
rotateSelected(1);
|
rotateSelected(1);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
}
|
}
|
||||||
|
@ -1793,7 +1851,7 @@ void SkeletonGraphicsWidget::shortcutRotateSelectedBy1()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutMoveSelectedToLeft()
|
void SkeletonGraphicsWidget::shortcutMoveSelectedToLeft()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode) {
|
if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) {
|
||||||
if (m_checkedOriginItem) {
|
if (m_checkedOriginItem) {
|
||||||
moveCheckedOrigin(-1, 0);
|
moveCheckedOrigin(-1, 0);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
|
@ -1806,7 +1864,7 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToLeft()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutMoveSelectedToRight()
|
void SkeletonGraphicsWidget::shortcutMoveSelectedToRight()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode) {
|
if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) {
|
||||||
if (m_checkedOriginItem) {
|
if (m_checkedOriginItem) {
|
||||||
moveCheckedOrigin(1, 0);
|
moveCheckedOrigin(1, 0);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
|
@ -1819,7 +1877,7 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToRight()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutMoveSelectedToUp()
|
void SkeletonGraphicsWidget::shortcutMoveSelectedToUp()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode) {
|
if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) {
|
||||||
if (m_checkedOriginItem) {
|
if (m_checkedOriginItem) {
|
||||||
moveCheckedOrigin(0, -1);
|
moveCheckedOrigin(0, -1);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
|
@ -1832,7 +1890,7 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToUp()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutMoveSelectedToDown()
|
void SkeletonGraphicsWidget::shortcutMoveSelectedToDown()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode) {
|
if (SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) {
|
||||||
if (m_checkedOriginItem) {
|
if (m_checkedOriginItem) {
|
||||||
moveCheckedOrigin(0, 1);
|
moveCheckedOrigin(0, 1);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
|
@ -1845,7 +1903,8 @@ void SkeletonGraphicsWidget::shortcutMoveSelectedToDown()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutScaleSelectedByMinus1()
|
void SkeletonGraphicsWidget::shortcutScaleSelectedByMinus1()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) {
|
if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) &&
|
||||||
|
hasSelection()) {
|
||||||
scaleSelected(-1);
|
scaleSelected(-1);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
}
|
}
|
||||||
|
@ -1853,7 +1912,8 @@ void SkeletonGraphicsWidget::shortcutScaleSelectedByMinus1()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutScaleSelectedBy1()
|
void SkeletonGraphicsWidget::shortcutScaleSelectedBy1()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) {
|
if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) &&
|
||||||
|
hasSelection()) {
|
||||||
scaleSelected(1);
|
scaleSelected(1);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
}
|
}
|
||||||
|
@ -1861,7 +1921,8 @@ void SkeletonGraphicsWidget::shortcutScaleSelectedBy1()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutSwitchProfileOnSelected()
|
void SkeletonGraphicsWidget::shortcutSwitchProfileOnSelected()
|
||||||
{
|
{
|
||||||
if (SkeletonDocumentEditMode::Select == m_document->editMode && hasSelection()) {
|
if ((SkeletonDocumentEditMode::Select == m_document->editMode || SkeletonDocumentEditMode::Mark == m_document->editMode) &&
|
||||||
|
hasSelection()) {
|
||||||
switchProfileOnRangeSelection();
|
switchProfileOnRangeSelection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1882,7 +1943,8 @@ void SkeletonGraphicsWidget::shortcutShowOrHideSelectedPart()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutEnableOrDisableSelectedPart()
|
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);
|
const SkeletonPart *part = m_document->findPart(m_lastCheckedPart);
|
||||||
bool partDisabled = part && part->disabled;
|
bool partDisabled = part && part->disabled;
|
||||||
emit setPartDisableState(m_lastCheckedPart, !partDisabled);
|
emit setPartDisableState(m_lastCheckedPart, !partDisabled);
|
||||||
|
@ -1892,7 +1954,8 @@ void SkeletonGraphicsWidget::shortcutEnableOrDisableSelectedPart()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutLockOrUnlockSelectedPart()
|
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);
|
const SkeletonPart *part = m_document->findPart(m_lastCheckedPart);
|
||||||
bool partLocked = part && part->locked;
|
bool partLocked = part && part->locked;
|
||||||
emit setPartLockState(m_lastCheckedPart, !partLocked);
|
emit setPartLockState(m_lastCheckedPart, !partLocked);
|
||||||
|
@ -1902,7 +1965,8 @@ void SkeletonGraphicsWidget::shortcutLockOrUnlockSelectedPart()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutXmirrorOnOrOffSelectedPart()
|
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);
|
const SkeletonPart *part = m_document->findPart(m_lastCheckedPart);
|
||||||
bool partXmirrored = part && part->xMirrored;
|
bool partXmirrored = part && part->xMirrored;
|
||||||
emit setPartXmirrorState(m_lastCheckedPart, !partXmirrored);
|
emit setPartXmirrorState(m_lastCheckedPart, !partXmirrored);
|
||||||
|
@ -1912,7 +1976,8 @@ void SkeletonGraphicsWidget::shortcutXmirrorOnOrOffSelectedPart()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutSubdivedOrNotSelectedPart()
|
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);
|
const SkeletonPart *part = m_document->findPart(m_lastCheckedPart);
|
||||||
bool partSubdived = part && part->subdived;
|
bool partSubdived = part && part->subdived;
|
||||||
emit setPartSubdivState(m_lastCheckedPart, !partSubdived);
|
emit setPartSubdivState(m_lastCheckedPart, !partSubdived);
|
||||||
|
@ -1922,7 +1987,8 @@ void SkeletonGraphicsWidget::shortcutSubdivedOrNotSelectedPart()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutChamferedOrNotSelectedPart()
|
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);
|
const SkeletonPart *part = m_document->findPart(m_lastCheckedPart);
|
||||||
bool partChamfered = part && part->chamfered;
|
bool partChamfered = part && part->chamfered;
|
||||||
emit setPartChamferState(m_lastCheckedPart, !partChamfered);
|
emit setPartChamferState(m_lastCheckedPart, !partChamfered);
|
||||||
|
@ -1937,7 +2003,8 @@ void SkeletonGraphicsWidget::shortcutSelectAll()
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::shortcutRoundEndOrNotSelectedPart()
|
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);
|
const SkeletonPart *part = m_document->findPart(m_lastCheckedPart);
|
||||||
bool partRounded = part && part->rounded;
|
bool partRounded = part && part->rounded;
|
||||||
emit setPartRoundState(m_lastCheckedPart, !partRounded);
|
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
|
class SkeletonGraphicsNodeItem : public QGraphicsEllipseItem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -441,6 +503,7 @@ signals:
|
||||||
void shortcutToggleFlatShading();
|
void shortcutToggleFlatShading();
|
||||||
void shortcutToggleRotation();
|
void shortcutToggleRotation();
|
||||||
void createGriddedPartsFromNodes(const std::set<QUuid> &nodeIds);
|
void createGriddedPartsFromNodes(const std::set<QUuid> &nodeIds);
|
||||||
|
void addPartByPolygons(const QPolygonF &mainProfile, const QPolygonF &sideProfile, const QSizeF &canvasSize);
|
||||||
public:
|
public:
|
||||||
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
||||||
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
||||||
|
@ -544,6 +607,7 @@ public slots:
|
||||||
void createWrapParts();
|
void createWrapParts();
|
||||||
void shortcutDelete();
|
void shortcutDelete();
|
||||||
void shortcutAddMode();
|
void shortcutAddMode();
|
||||||
|
void shortcutMarkMode();
|
||||||
void shortcutUndo();
|
void shortcutUndo();
|
||||||
void shortcutRedo();
|
void shortcutRedo();
|
||||||
void shortcutXlock();
|
void shortcutXlock();
|
||||||
|
@ -618,7 +682,9 @@ private: //need initalize
|
||||||
float m_lastAddedY;
|
float m_lastAddedY;
|
||||||
float m_lastAddedZ;
|
float m_lastAddedZ;
|
||||||
SkeletonGraphicsSelectionItem *m_selectionItem;
|
SkeletonGraphicsSelectionItem *m_selectionItem;
|
||||||
|
SkeletonGraphicsMarkerItem *m_markerItem;
|
||||||
bool m_rangeSelectionStarted;
|
bool m_rangeSelectionStarted;
|
||||||
|
bool m_markerStarted;
|
||||||
bool m_mouseEventFromSelf;
|
bool m_mouseEventFromSelf;
|
||||||
bool m_moveHappened;
|
bool m_moveHappened;
|
||||||
int m_lastRot;
|
int m_lastRot;
|
||||||
|
|
|
@ -155,8 +155,10 @@ void StrokeModifier::finalize()
|
||||||
size_t newInsertNum = currentEdgeLength / targetEdgeLength;
|
size_t newInsertNum = currentEdgeLength / targetEdgeLength;
|
||||||
if (newInsertNum < 1)
|
if (newInsertNum < 1)
|
||||||
newInsertNum = 1;
|
newInsertNum = 1;
|
||||||
if (newInsertNum > 100)
|
if (newInsertNum > 100) {
|
||||||
|
addEdge(edge.firstNodeIndex, edge.secondNodeIndex);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
float stepFactor = 1.0 / (newInsertNum + 1);
|
float stepFactor = 1.0 / (newInsertNum + 1);
|
||||||
std::vector<size_t> nodeIndices;
|
std::vector<size_t> nodeIndices;
|
||||||
nodeIndices.push_back(edge.firstNodeIndex);
|
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()));
|
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;
|
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 &v2 = vertices[sourceTriangle[1]];
|
||||||
const auto &v3 = vertices[sourceTriangle[2]];
|
const auto &v3 = vertices[sourceTriangle[2]];
|
||||||
float area = areaOfTriangle(v1, v2, v3);
|
float area = areaOfTriangle(v1, v2, v3);
|
||||||
float angles[] = {angleBetweenVectors(v2-v1, v3-v1),
|
float angles[] = {degreesBetweenVectors(v2-v1, v3-v1),
|
||||||
angleBetweenVectors(v1-v2, v3-v2),
|
degreesBetweenVectors(v1-v2, v3-v2),
|
||||||
angleBetweenVectors(v1-v3, v2-v3)};
|
degreesBetweenVectors(v1-v3, v2-v3)};
|
||||||
for (int i = 0; i < 3; ++i) {
|
for (int i = 0; i < 3; ++i) {
|
||||||
if (sourceTriangle[i] >= vertices.size()) {
|
if (sourceTriangle[i] >= vertices.size()) {
|
||||||
qDebug() << "Invalid vertex index" << sourceTriangle[i] << "vertices size" << 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;
|
float degrees = 0;
|
||||||
auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first});
|
auto findDegreesResult = degreesBetweenFacesMap.find({triangleVertex.first, otherTriangleVertex.first});
|
||||||
if (findDegreesResult == degreesBetweenFacesMap.end()) {
|
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({{triangleVertex.first, otherTriangleVertex.first}, degrees});
|
||||||
degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees});
|
degreesBetweenFacesMap.insert({{otherTriangleVertex.first, triangleVertex.first}, degrees});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,7 +23,7 @@ QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
|
||||||
QString unifiedWindowTitle(const QString &text);
|
QString unifiedWindowTitle(const QString &text);
|
||||||
QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t);
|
QQuaternion quaternionOvershootSlerp(const QQuaternion &q0, const QQuaternion &q1, float t);
|
||||||
float radianBetweenVectors(const QVector3D &first, const QVector3D &second);
|
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);
|
float areaOfTriangle(const QVector3D &a, const QVector3D &b, const QVector3D &c);
|
||||||
QQuaternion eulerAnglesToQuaternion(double pitch, double yaw, double roll);
|
QQuaternion eulerAnglesToQuaternion(double pitch, double yaw, double roll);
|
||||||
void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, double *roll);
|
void quaternionToEulerAngles(const QQuaternion &q, double *pitch, double *yaw, double *roll);
|
||||||
|
|
Loading…
Reference in New Issue