From 8a79fa06de7f6fd5395f4f7414707febe635d5c3 Mon Sep 17 00:00:00 2001 From: huxingyi Date: Tue, 20 Oct 2020 20:48:08 +0930 Subject: [PATCH] Add part path smooth interpolation Part path set default as Centripetal Catmull-Rom spline interpolated --- dust3d.pro | 3 + resources/model-mosquito.ds3 | 208 ++++++++++++++-------------- src/centripetalcatmullromspline.cpp | 135 ++++++++++++++++++ src/centripetalcatmullromspline.h | 31 +++++ src/document.cpp | 18 +++ src/document.h | 2 + src/documentwindow.cpp | 1 + src/meshgenerator.cpp | 3 + src/meshgenerator.h | 1 - src/parttreewidget.cpp | 11 ++ src/parttreewidget.h | 1 + src/partwidget.cpp | 36 ++++- src/partwidget.h | 3 + src/skeletondocument.h | 8 +- src/strokemodifier.cpp | 188 +++++++++++++++++++++++++ src/strokemodifier.h | 3 + 16 files changed, 543 insertions(+), 109 deletions(-) create mode 100644 src/centripetalcatmullromspline.cpp create mode 100644 src/centripetalcatmullromspline.h diff --git a/dust3d.pro b/dust3d.pro index 37b10757..8a06ab55 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -541,6 +541,9 @@ HEADERS += src/partpreviewimagesgenerator.h SOURCES += src/remeshhole.cpp HEADERS += src/remeshhole.h +SOURCES += src/centripetalcatmullromspline.cpp +HEADERS += src/centripetalcatmullromspline.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/resources/model-mosquito.ds3 b/resources/model-mosquito.ds3 index 66975273..e413d5a2 100644 --- a/resources/model-mosquito.ds3 +++ b/resources/model-mosquito.ds3 @@ -1,120 +1,120 @@ DUST3D 1.0 xml 0000000193 - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + diff --git a/src/centripetalcatmullromspline.cpp b/src/centripetalcatmullromspline.cpp new file mode 100644 index 00000000..a775bd3b --- /dev/null +++ b/src/centripetalcatmullromspline.cpp @@ -0,0 +1,135 @@ +#include +#include "centripetalcatmullromspline.h" + +CentripetalCatmullRomSpline::CentripetalCatmullRomSpline(bool isClosed) : + m_isClosed(isClosed) +{ +} + +const std::vector &CentripetalCatmullRomSpline::splineNodes() +{ + return m_splineNodes; +} + +void CentripetalCatmullRomSpline::addPoint(int source, const QVector3D &position, bool isKnot) +{ + if (isKnot) + m_splineKnots.push_back(m_splineNodes.size()); + m_splineNodes.push_back({ + source, position + }); +} + +float CentripetalCatmullRomSpline::atKnot(float t, const QVector3D &p0, const QVector3D &p1) +{ + const float alpha = 0.5f; + QVector3D d = p1 - p0; + float a = QVector3D::dotProduct(d, d); + float b = std::pow(a, alpha * 0.5f); + return (b + t); +} + +void CentripetalCatmullRomSpline::interpolateSegment(std::vector &knots, + size_t from, size_t to) +{ + const QVector3D &p0 = knots[0]; + const QVector3D &p1 = knots[1]; + const QVector3D &p2 = knots[2]; + const QVector3D &p3 = knots[3]; + + float s1 = (p2 - p1).length(); + + float t0 = 0.0f; + float t1 = atKnot(t0, p0, p1); + float t2 = atKnot(t1, p1, p2); + float t3 = atKnot(t2, p2, p3); + + for (size_t index = (from + 1) % m_splineNodes.size(); index != to; index = (index + 1) % m_splineNodes.size()) { + auto &position = m_splineNodes[index].position; + + float factor = (position - p1).length() / s1; + float t = t1 * (1.0f - factor) + t2 * factor; + + QVector3D a1 = (t1 - t) / (t1 - t0) * p0 + (t - t0) / (t1 - t0) * p1; + QVector3D a2 = (t2 - t) / (t2 - t1) * p1 + (t - t1) / (t2 - t1) * p2; + QVector3D a3 = (t3 - t) / (t3 - t2) * p2 + (t - t2) / (t3 - t2) * p3; + + QVector3D b1 = (t2 - t) / (t2 - t0) * a1 + (t - t0) / (t2 - t0) * a2; + QVector3D b2 = (t3 - t) / (t3 - t1) * a2 + (t - t1) / (t3 - t1) * a3; + + position = ((t2 - t) / (t2 - t1) * b1 + (t - t1) / (t2 - t1) * b2); + } +} + +bool CentripetalCatmullRomSpline::interpolate() +{ + if (m_isClosed) + return interpolateClosed(); + + return interpolateOpened(); +} + +bool CentripetalCatmullRomSpline::interpolateOpened() +{ + if (m_splineKnots.size() < 3) + return false; + + { + std::vector knots = { + (m_splineNodes[m_splineKnots[0]].position + (m_splineNodes[m_splineKnots[0]].position - m_splineNodes[m_splineKnots[1]].position)), + m_splineNodes[m_splineKnots[0]].position, + m_splineNodes[m_splineKnots[1]].position, + m_splineNodes[m_splineKnots[2]].position + }; + interpolateSegment(knots, m_splineKnots[0], m_splineKnots[1]); + } + + { + size_t tail = m_splineKnots.size() - 1; + std::vector knots = { + m_splineNodes[m_splineKnots[tail - 2]].position, + m_splineNodes[m_splineKnots[tail - 1]].position, + m_splineNodes[m_splineKnots[tail]].position, + (m_splineNodes[m_splineKnots[tail]].position + (m_splineNodes[m_splineKnots[tail]].position - m_splineNodes[m_splineKnots[tail - 1]].position)) + }; + interpolateSegment(knots, m_splineKnots[tail - 1], m_splineKnots[tail]); + } + + for (size_t i = 1; i + 2 < m_splineKnots.size(); ++i) { + size_t h = i - 1; + size_t j = i + 1; + size_t k = i + 2; + + std::vector knots = { + m_splineNodes[m_splineKnots[h]].position, + m_splineNodes[m_splineKnots[i]].position, + m_splineNodes[m_splineKnots[j]].position, + m_splineNodes[m_splineKnots[k]].position + }; + interpolateSegment(knots, m_splineKnots[i], m_splineKnots[j]); + } + + return true; +} + +bool CentripetalCatmullRomSpline::interpolateClosed() +{ + if (m_splineKnots.size() < 3) + return false; + + for (size_t h = 0; h < m_splineKnots.size(); ++h) { + size_t i = (h + 1) % m_splineKnots.size(); + size_t j = (h + 2) % m_splineKnots.size(); + size_t k = (h + 3) % m_splineKnots.size(); + + std::vector knots = { + m_splineNodes[m_splineKnots[h]].position, + m_splineNodes[m_splineKnots[i]].position, + m_splineNodes[m_splineKnots[j]].position, + m_splineNodes[m_splineKnots[k]].position + }; + interpolateSegment(knots, m_splineKnots[i], m_splineKnots[j]); + } + + return true; +} \ No newline at end of file diff --git a/src/centripetalcatmullromspline.h b/src/centripetalcatmullromspline.h new file mode 100644 index 00000000..5984a22c --- /dev/null +++ b/src/centripetalcatmullromspline.h @@ -0,0 +1,31 @@ +#ifndef DUST3D_CENTRIPETAL_CATMULL_ROM_SPLINE_H +#define DUST3D_CENTRIPETAL_CATMULL_ROM_SPLINE_H +#include +#include + +class CentripetalCatmullRomSpline +{ +public: + struct SplineNode + { + int source = -1; + QVector3D position; + }; + + CentripetalCatmullRomSpline(bool isClosed); + void addPoint(int source, const QVector3D &position, bool isKnot); + bool interpolate(); + const std::vector &splineNodes(); +private: + std::vector m_splineNodes; + std::vector m_splineKnots; + + bool m_isClosed = false; + bool interpolateClosed(); + bool interpolateOpened(); + float atKnot(float t, const QVector3D &p0, const QVector3D &p1); + void interpolateSegment(std::vector &knots, + size_t from, size_t to); +}; + +#endif diff --git a/src/document.cpp b/src/document.cpp index 785cb94a..30003bc1 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -1197,6 +1197,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId part["materialId"] = partIt.second.materialId.toString(); if (partIt.second.countershaded) part["countershaded"] = "true"; + if (partIt.second.smooth) + part["smooth"] = "true"; //if (partIt.second.gridded) // part["gridded"] = "true"; snapshot->parts[part["id"]] = part; @@ -1629,6 +1631,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, enum SnapshotSource sou if (materialIdIt != partKv.second.end()) part.materialId = oldNewIdMap[QUuid(materialIdIt->second)]; part.countershaded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "countershaded")); + part.smooth = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "smooth")); //part.gridded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "gridded"));; newAddedPartIds.insert(part.id); } @@ -3166,6 +3169,21 @@ void Document::setPartCountershaded(QUuid partId, bool countershaded) emit textureChanged(); } +void Document::setPartSmoothState(QUuid partId, bool smooth) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + if (part->second.smooth == smooth) + return; + part->second.smooth = smooth; + part->second.dirty = true; + emit partSmoothStateChanged(partId); + emit skeletonChanged(); +} + void Document::setPartCutRotation(QUuid partId, float cutRotation) { auto part = partMap.find(partId); diff --git a/src/document.h b/src/document.h index 3a57d530..3da8b4c6 100644 --- a/src/document.h +++ b/src/document.h @@ -474,6 +474,7 @@ signals: void partRoughnessChanged(QUuid partId); void partHollowThicknessChanged(QUuid partId); void partCountershadeStateChanged(QUuid partId); + void partSmoothStateChanged(QUuid partId); void partGridStateChanged(QUuid partId); void componentCombineModeChanged(QUuid componentId); void cleanup(); @@ -683,6 +684,7 @@ public slots: void setPartRoughness(QUuid partId, float roughness); void setPartHollowThickness(QUuid partId, float hollowThickness); void setPartCountershaded(QUuid partId, bool countershaded); + void setPartSmoothState(QUuid partId, bool smooth); void setComponentCombineMode(QUuid componentId, CombineMode combineMode); void moveComponentUp(QUuid componentId); void moveComponentDown(QUuid componentId); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 04153348..80021d91 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -1219,6 +1219,7 @@ DocumentWindow::DocumentWindow() : connect(m_document, &Document::partMetalnessChanged, m_partTreeWidget, &PartTreeWidget::partMetalnessChanged); connect(m_document, &Document::partRoughnessChanged, m_partTreeWidget, &PartTreeWidget::partRoughnessChanged); connect(m_document, &Document::partCountershadeStateChanged, m_partTreeWidget, &PartTreeWidget::partCountershadeStateChanged); + connect(m_document, &Document::partSmoothStateChanged, m_partTreeWidget, &PartTreeWidget::partSmoothStateChanged); connect(m_document, &Document::partTargetChanged, m_partTreeWidget, &PartTreeWidget::partXmirrorStateChanged); connect(m_document, &Document::partTargetChanged, m_partTreeWidget, &PartTreeWidget::partColorStateChanged); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index befbc310..fe0610b1 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -352,6 +352,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString, bool rounded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "rounded")); bool chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(part, "chamfered")); bool countershaded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "countershaded")); + bool smooth = isTrueValueString(valueOfKeyInMapOrEmpty(part, "smooth")); QString colorString = valueOfKeyInMapOrEmpty(part, "color"); QColor partColor = colorString.isEmpty() ? m_defaultPartColor : QColor(colorString); float deformThickness = 1.0; @@ -584,6 +585,8 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString, strokeModifier = new StrokeModifier; + if (smooth) + strokeModifier->enableSmooth(); if (addIntermediateNodes) strokeModifier->enableIntermediateAddition(); diff --git a/src/meshgenerator.h b/src/meshgenerator.h index e0fc54b7..0985c5c7 100644 --- a/src/meshgenerator.h +++ b/src/meshgenerator.h @@ -13,7 +13,6 @@ #include "model.h" #include "componentlayer.h" #include "clothforce.h" -#include "strokemodifier.h" class GeneratedPart { diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp index 9c4c0511..8d5e7671 100644 --- a/src/parttreewidget.cpp +++ b/src/parttreewidget.cpp @@ -1475,6 +1475,17 @@ void PartTreeWidget::partCountershadeStateChanged(QUuid partId) widget->updateColorButton(); } +void PartTreeWidget::partSmoothStateChanged(QUuid partId) +{ + auto item = m_partItemMap.find(partId); + if (item == m_partItemMap.end()) { + qDebug() << "Part item not found:" << partId; + return; + } + PartWidget *widget = (PartWidget *)itemWidget(item->second, 0); + widget->updateSmoothButton(); +} + void PartTreeWidget::partChecked(QUuid partId) { auto item = m_partItemMap.find(partId); diff --git a/src/parttreewidget.h b/src/parttreewidget.h index 9f0d5b7e..2fe48222 100644 --- a/src/parttreewidget.h +++ b/src/parttreewidget.h @@ -82,6 +82,7 @@ public slots: void partMetalnessChanged(QUuid partId); void partRoughnessChanged(QUuid partId); void partCountershadeStateChanged(QUuid partId); + void partSmoothStateChanged(QUuid partId); void partChecked(QUuid partId); void partUnchecked(QUuid partId); void partComponentChecked(QUuid partId); diff --git a/src/partwidget.cpp b/src/partwidget.cpp index f19902fe..29290a12 100644 --- a/src/partwidget.cpp +++ b/src/partwidget.cpp @@ -38,6 +38,11 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : m_visibleButton->setSizePolicy(retainSizePolicy); initButton(m_visibleButton); + m_smoothButton = new QPushButton(); + m_smoothButton->setToolTip(tr("Toggle smooth")); + m_smoothButton->setSizePolicy(retainSizePolicy); + initButton(m_smoothButton); + m_lockButton = new QPushButton(); m_lockButton->setToolTip(tr("Lock/unlock nodes")); m_lockButton->setSizePolicy(retainSizePolicy); @@ -109,7 +114,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : toolsLayout->setContentsMargins(0, 0, 5, 0); int row = 0; int col = 0; - toolsLayout->addWidget(m_visibleButton, row, col++, Qt::AlignBottom); + toolsLayout->addWidget(m_smoothButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_lockButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_disableButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_xMirrorButton, row, col++, Qt::AlignBottom); @@ -128,7 +133,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : QHBoxLayout *previewAndToolsLayout = new QHBoxLayout; previewAndToolsLayout->setSpacing(0); previewAndToolsLayout->setContentsMargins(0, 0, 0, 0); - //previewAndToolsLayout->addWidget(m_visibleButton); + previewAndToolsLayout->addWidget(m_visibleButton); //previewAndToolsLayout->addWidget(m_previewWidget); previewAndToolsLayout->addWidget(m_previewLabel); previewAndToolsLayout->addLayout(toolsLayout); @@ -182,12 +187,23 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : connect(this, &PartWidget::setPartRoughness, m_document, &Document::setPartRoughness); connect(this, &PartWidget::setPartHollowThickness, m_document, &Document::setPartHollowThickness); connect(this, &PartWidget::setPartCountershaded, m_document, &Document::setPartCountershaded); + connect(this, &PartWidget::setPartSmoothState, m_document, &Document::setPartSmoothState); connect(this, &PartWidget::checkPart, m_document, &Document::checkPart); connect(this, &PartWidget::enableBackgroundBlur, m_document, &Document::enableBackgroundBlur); connect(this, &PartWidget::disableBackgroundBlur, m_document, &Document::disableBackgroundBlur); connect(this, &PartWidget::groupOperationAdded, m_document, &Document::saveSnapshot); + connect(m_smoothButton, &QPushButton::clicked, [=]() { + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + emit setPartSmoothState(m_partId, !part->smooth); + emit groupOperationAdded(); + }); + connect(m_lockButton, &QPushButton::clicked, [=]() { const SkeletonPart *part = m_document->findPart(m_partId); if (!part) { @@ -313,12 +329,13 @@ ModelWidget *PartWidget::previewWidget() QSize PartWidget::preferredSize() { - return QSize(Theme::miniIconSize + Theme::partPreviewImageSize + Theme::miniIconSize * 4 + 5 + 2, Theme::partPreviewImageSize + 6); + return QSize(Theme::miniIconSize + Theme::partPreviewImageSize + Theme::miniIconSize * 5 + 5 + 2, Theme::partPreviewImageSize + 6); } void PartWidget::updateAllButtons() { updateVisibleButton(); + updateSmoothButton(); updateLockButton(); updateSubdivButton(); updateDisableButton(); @@ -828,6 +845,19 @@ void PartWidget::updateLockButton() updateButton(m_lockButton, QChar(fa::unlock), false); } +void PartWidget::updateSmoothButton() +{ + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + if (part->smooth) + updateButton(m_smoothButton, QChar(fa::headphones), true); + else + updateButton(m_smoothButton, QChar(fa::headphones), false); +} + void PartWidget::updateVisibleButton() { const SkeletonPart *part = m_document->findPart(m_partId); diff --git a/src/partwidget.h b/src/partwidget.h index 08631928..69e7df8f 100644 --- a/src/partwidget.h +++ b/src/partwidget.h @@ -33,6 +33,7 @@ signals: void setPartRoughness(QUuid partId, float roughness); void setPartHollowThickness(QUuid partId, float hollowThickness); void setPartCountershaded(QUuid partId, bool countershaded); + void setPartSmoothState(QUuid partId, bool smooth); void movePartUp(QUuid partId); void movePartDown(QUuid partId); void movePartToTop(QUuid partId); @@ -46,6 +47,7 @@ public: void reload(); void updatePreview(); void updateLockButton(); + void updateSmoothButton(); void updateVisibleButton(); void updateSubdivButton(); void updateDisableButton(); @@ -73,6 +75,7 @@ private: // need initialize private: ModelWidget *m_previewWidget = nullptr; QPushButton *m_visibleButton = nullptr; + QPushButton *m_smoothButton = nullptr; QPushButton *m_lockButton = nullptr; QPushButton *m_subdivButton = nullptr; QPushButton *m_disableButton = nullptr; diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 96126216..cf64fff4 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -188,6 +188,7 @@ public: QUuid fillMeshLinkedId; bool isPreviewMeshObsolete; QPixmap previewPixmap; + bool smooth; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), @@ -214,7 +215,8 @@ public: hollowThickness(0.0), countershaded(false), gridded(false), - isPreviewMeshObsolete(false) + isPreviewMeshObsolete(false), + smooth(true) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -396,6 +398,10 @@ public: target = other.target; colorSolubility = other.colorSolubility; countershaded = other.countershaded; + metalness = other.metalness; + roughness = other.roughness; + deformUnified = other.deformUnified; + smooth = other.smooth; } void updatePreviewMesh(Model *previewMesh) { diff --git a/src/strokemodifier.cpp b/src/strokemodifier.cpp index d8b8d73d..829e3c13 100644 --- a/src/strokemodifier.cpp +++ b/src/strokemodifier.cpp @@ -1,13 +1,20 @@ #include #include +#include #include "strokemodifier.h" #include "util.h" +#include "centripetalcatmullromspline.h" void StrokeModifier::enableIntermediateAddition() { m_intermediateAdditionEnabled = true; } +void StrokeModifier::enableSmooth() +{ + m_smooth = true; +} + size_t StrokeModifier::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) { size_t nodeIndex = m_nodes.size(); @@ -177,6 +184,187 @@ void StrokeModifier::finalize() addEdge(nodeIndices[i - 1], nodeIndices[i]); } } + + if (m_smooth) + smooth(); +} + +void StrokeModifier::smooth() +{ + std::unordered_map> neighborMap; + for (const auto &edge: m_edges) { + neighborMap[edge.firstNodeIndex].push_back(edge.secondNodeIndex); + neighborMap[edge.secondNodeIndex].push_back(edge.firstNodeIndex); + } + + int startEndpoint = 0; + for (const auto &edge: m_edges) { + auto findNeighbor = neighborMap.find(edge.firstNodeIndex); + if (findNeighbor == neighborMap.end()) + continue; + if (1 != findNeighbor->second.size()) { + auto findNeighborNeighbor = neighborMap.find(edge.secondNodeIndex); + if (findNeighborNeighbor == neighborMap.end()) + continue; + if (1 != findNeighborNeighbor->second.size()) + continue; + startEndpoint = edge.secondNodeIndex; + } else { + startEndpoint = edge.firstNodeIndex; + } + break; + } + if (-1 == startEndpoint) + return; + + int loopIndex = startEndpoint; + int previousIndex = -1; + bool isRing = false; + std::vector loop; + while (-1 != loopIndex) { + loop.push_back(loopIndex); + auto findNeighbor = neighborMap.find(loopIndex); + if (findNeighbor == neighborMap.end()) + return; + int nextIndex = -1; + for (const auto &index: findNeighbor->second) { + if (index == previousIndex) + continue; + if (index == startEndpoint) { + isRing = true; + break; + } + nextIndex = index; + break; + } + previousIndex = loopIndex; + loopIndex = nextIndex; + } + + CentripetalCatmullRomSpline spline(isRing); + for (size_t i = 0; i < loop.size(); ++i) { + const auto &nodeIndex = loop[i]; + const auto &node = m_nodes[nodeIndex]; + bool isKnot = node.originNodeIndex == nodeIndex; + qDebug() << "Loop[" << i << "]: isKnot:" << (isKnot ? "TRUE" : "false") << "nodeIndex:" << nodeIndex; + spline.addPoint((int)nodeIndex, node.position, isKnot); + } + if (!spline.interpolate()) + return; + for (const auto &it: spline.splineNodes()) { + if (-1 == it.source) + continue; + auto &node = m_nodes[it.source]; + node.position = it.position; + } + + /* + auto atKnot = [](float t, const QVector3D &p0, const QVector3D &p1) { + const float alpha = 0.5f; + QVector3D d = p1 - p0; + float a = QVector3D::dotProduct(d, d); + float b = std::pow(a, alpha * 0.5f); + return (b + t); + }; + + auto centripetalCatmullRom = [&](std::vector &knots, + size_t segment, const std::vector ×, const std::vector &nodeIndices) { + const QVector3D &p0 = m_nodes[knots[0]].position; + const QVector3D &p1 = m_nodes[knots[1]].position; + const QVector3D &p2 = m_nodes[knots[2]].position; + const QVector3D &p3 = m_nodes[knots[3]].position; + + float t0 = 0.0f; + float t1 = atKnot(t0, p0, p1); + float t2 = atKnot(t1, p1, p2); + float t3 = atKnot(t2, p2, p3); + + qDebug() << "t0:" << t0; + qDebug() << "t1:" << t1; + qDebug() << "t2:" << t2; + qDebug() << "t3:" << t3; + + qDebug() << "p0:" << p0; + qDebug() << "p1:" << p1; + qDebug() << "p2:" << p2; + qDebug() << "p3:" << p3; + + for (size_t i = 0; i < times.size(); ++i) { + const auto &factor = times[i]; + float t = 0.0; + if (0 == segment) + t = t0 * (1.0f - factor) + t1 * factor; + else if (1 == segment) + t = t1 * (1.0f - factor) + t2 * factor; + else + t = t2 * (1.0f - factor) + t3 * factor; + QVector3D a1 = (t1 - t) / (t1 - t0) * p0 + (t - t0) / (t1 - t0) * p1; + QVector3D a2 = (t2 - t) / (t2 - t1) * p1 + (t - t1) / (t2 - t1) * p2; + QVector3D a3 = (t3 - t) / (t3 - t2) * p2 + (t - t2) / (t3 - t2) * p3; + + QVector3D b1 = (t2 - t) / (t2 - t0) * a1 + (t - t0) / (t2 - t0) * a2; + QVector3D b2 = (t3 - t) / (t3 - t1) * a2 + (t - t1) / (t3 - t1) * a3; + + m_nodes[nodeIndices[i]].position = ((t2 - t) / (t2 - t1) * b1 + (t - t1) / (t2 - t1) * b2); + + qDebug() << "Update node position:" << m_nodes[nodeIndices[i]].position << "factor:" << factor << "t:" << t; + } + }; + + struct SplineNode + { + size_t order; + int nodeIndex; + float distance; + }; + std::vector splineNodes; + std::vector splineKnots; + float distance = 0; + for (size_t i = 0; i < loop.size(); ++i) { + const auto &nodeIndex = loop[i]; + const auto &node = m_nodes[nodeIndex]; + if (i > 0) + distance += (m_nodes[loop[i - 1]].position - node.position).length(); + if (node.originNodeIndex == nodeIndex) + splineKnots.push_back(splineNodes.size()); + splineNodes.push_back({ + i, nodeIndex, distance + }); + } + + qDebug() << "splineNodes.size():" << splineNodes.size(); + qDebug() << "splineKnots.size():" << splineKnots.size(); + + for (size_t i = 0; i < splineKnots.size(); ++i) { + size_t h = i > 0 ? i - 1 : i; + size_t j = i + 1 < splineKnots.size() ? i + 1 : i; + size_t k = i + 2 < splineKnots.size() ? i + 2 : j; + qDebug() << "h:" << h; + qDebug() << "i:" << i; + qDebug() << "j:" << j; + qDebug() << "k:" << k; + if (h == i || i == j || j == k) + continue; + float beginDistance = splineNodes[splineKnots[i]].distance; + float endDistance = splineNodes[splineKnots[j]].distance; + float totalDistance = endDistance - beginDistance; + std::vector times; + std::vector nodeIndices; + for (size_t splineNodeIndex = splineKnots[i] + 1; + splineNodeIndex < splineKnots[j]; ++splineNodeIndex) { + qDebug() << "splineNodeIndex:" << splineNodeIndex; + times.push_back((splineNodes[splineNodeIndex].distance - beginDistance) / totalDistance); + nodeIndices.push_back(splineNodes[splineNodeIndex].nodeIndex); + } + std::vector knots = { + splineNodes[splineKnots[h]].nodeIndex, + splineNodes[splineKnots[i]].nodeIndex, + splineNodes[splineKnots[j]].nodeIndex, + splineNodes[splineKnots[k]].nodeIndex + }; + centripetalCatmullRom(knots, 1, times, nodeIndices); + } + */ } const std::vector &StrokeModifier::nodes() const diff --git a/src/strokemodifier.h b/src/strokemodifier.h index a5c0dc0d..fe443489 100644 --- a/src/strokemodifier.h +++ b/src/strokemodifier.h @@ -30,6 +30,7 @@ public: void subdivide(); void roundEnd(); void enableIntermediateAddition(); + void enableSmooth(); const std::vector &nodes() const; const std::vector &edges() const; void finalize(); @@ -39,10 +40,12 @@ private: std::vector m_nodes; std::vector m_edges; bool m_intermediateAdditionEnabled = false; + bool m_smooth = false; void createIntermediateNode(const Node &firstNode, const Node &secondNode, float factor, Node *resultNode); float averageCutTemplateEdgeLength(const std::vector &cutTemplate); void createIntermediateCutTemplateEdges(std::vector &cutTemplate, float averageCutTemplateLength); + void smooth(); }; #endif