Implement marker pen

This commit introduce a new feature: Marker Pen
It allow user to quick draw a contour of the side view and got nodes automatically generated
master
Jeremy Hu 2019-12-25 08:54:49 +09:30
parent 7a5065865a
commit 9f55f738f3
23 changed files with 724 additions and 65 deletions

View File

@ -1219,3 +1219,9 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
<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>

View File

@ -34,6 +34,8 @@ Keyboard
+----------------------+--------------------------------------------------------------------------+
| D | Enter Paint Mode |
+----------------------+--------------------------------------------------------------------------+
| G | Enter Mark Mode |
+----------------------+--------------------------------------------------------------------------+
| CTRL + S | Save |
+----------------------+--------------------------------------------------------------------------+
| CTRL + Z | Undo |

View File

@ -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

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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

View File

@ -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"));

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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);

View File

@ -337,6 +337,7 @@ enum class SkeletonDocumentEditMode
{
Add = 0,
Select,
Mark,
Paint,
Drag,
ZoomIn,

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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 {

View File

@ -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);