diff --git a/docs/shortcuts.rst b/docs/shortcuts.rst index 085b9744..9e477701 100644 --- a/docs/shortcuts.rst +++ b/docs/shortcuts.rst @@ -66,8 +66,6 @@ Keyboard +----------------------+--------------------------------------------------------------------------+ | U | Toggle Part End Roundable | +----------------------+--------------------------------------------------------------------------+ -| W | Toggle Part Wrap Status: (W)rap using Convex hull/Normal | -+----------------------+--------------------------------------------------------------------------+ | E | Swith the Selected Nodes to Different Profile (Main / Side) | +----------------------+--------------------------------------------------------------------------+ diff --git a/src/document.cpp b/src/document.cpp index 9d6312a7..4d75fe07 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -859,7 +859,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId part["xMirrored"] = partIt.second.xMirrored ? "true" : "false"; part["zMirrored"] = partIt.second.zMirrored ? "true" : "false"; part["rounded"] = partIt.second.rounded ? "true" : "false"; - part["wrapped"] = partIt.second.wrapped ? "true" : "false"; + if (partIt.second.cutRotationAdjusted()) + part["cutRotation"] = QString::number(partIt.second.cutRotation); part["dirty"] = partIt.second.dirty ? "true" : "false"; if (partIt.second.hasColor) part["color"] = partIt.second.color.name(); @@ -1114,7 +1115,9 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) part.xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "xMirrored")); part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored")); part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded")); - part.wrapped = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "wrapped")); + const auto &cutRotationIt = partKv.second.find("cutRotation"); + if (cutRotationIt != partKv.second.end()) + part.setCutRotation(cutRotationIt->second.toFloat()); if (isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "inverse"))) inversePartIds.insert(part.id); const auto &colorIt = partKv.second.find("color"); @@ -2190,18 +2193,16 @@ void Document::setPartRoundState(QUuid partId, bool rounded) emit skeletonChanged(); } -void Document::setPartWrapState(QUuid partId, bool wrapped) +void Document::setPartCutRotation(QUuid partId, float cutRotation) { auto part = partMap.find(partId); if (part == partMap.end()) { qDebug() << "Part not found:" << partId; return; } - if (part->second.wrapped == wrapped) - return; - part->second.wrapped = wrapped; + part->second.setCutRotation(cutRotation); part->second.dirty = true; - emit partWrapStateChanged(partId); + emit partCutRotationChanged(partId); emit skeletonChanged(); } diff --git a/src/document.h b/src/document.h index c79e3519..c57de4de 100644 --- a/src/document.h +++ b/src/document.h @@ -400,7 +400,7 @@ signals: void partDeformWidthChanged(QUuid partId); void partRoundStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); - void partWrapStateChanged(QUuid partId); + void partCutRotationChanged(QUuid partId); void partMaterialIdChanged(QUuid partId); void componentCombineModeChanged(QUuid componentId); void cleanup(); @@ -553,7 +553,7 @@ public slots: void setPartDeformWidth(QUuid partId, float width); void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); - void setPartWrapState(QUuid partId, bool wrapped); + void setPartCutRotation(QUuid partId, float cutRotation); void setPartMaterialId(QUuid partId, QUuid materialId); void setComponentCombineMode(QUuid componentId, CombineMode combineMode); void moveComponentUp(QUuid componentId); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 1c3d454f..94f45fab 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -753,7 +753,7 @@ DocumentWindow::DocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::setPartDisableState, m_document, &Document::setPartDisableState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartXmirrorState, m_document, &Document::setPartXmirrorState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &Document::setPartRoundState); - connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartWrapState); + connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &Document::setPartCutRotation); connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &Document::setXlockState); connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &Document::setYlockState); @@ -831,7 +831,7 @@ DocumentWindow::DocumentWindow() : connect(m_document, &Document::partDeformThicknessChanged, partTreeWidget, &PartTreeWidget::partDeformChanged); connect(m_document, &Document::partDeformWidthChanged, partTreeWidget, &PartTreeWidget::partDeformChanged); connect(m_document, &Document::partRoundStateChanged, partTreeWidget, &PartTreeWidget::partRoundStateChanged); - connect(m_document, &Document::partWrapStateChanged, partTreeWidget, &PartTreeWidget::partWrapStateChanged); + connect(m_document, &Document::partCutRotationChanged, partTreeWidget, &PartTreeWidget::partWrapStateChanged); connect(m_document, &Document::partColorStateChanged, partTreeWidget, &PartTreeWidget::partColorStateChanged); connect(m_document, &Document::partMaterialIdChanged, partTreeWidget, &PartTreeWidget::partMaterialIdChanged); connect(m_document, &Document::partRemoved, partTreeWidget, &PartTreeWidget::partRemoved); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index 96958bdb..deaa8c6d 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -152,6 +153,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt QColor partColor = colorString.isEmpty() ? m_defaultPartColor : QColor(colorString); float deformThickness = 1.0; float deformWidth = 1.0; + float cutRotation = 0.0; + + std::vector cutTemplate = g_defaultCutTemplate; + QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation"); + if (!cutRotationString.isEmpty()) { + cutRotation = cutRotationString.toFloat(); + } QString thicknessString = valueOfKeyInMapOrEmpty(part, "deformThickness"); if (!thicknessString.isEmpty()) { @@ -247,7 +255,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt for (const auto &nodeIt: nodeInfos) { const auto &nodeIdString = nodeIt.first; const auto &nodeInfo = nodeIt.second; - size_t nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, g_defaultCutTemplate); + size_t nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate); nodeIdStringToIndexMap[nodeIdString] = nodeIndex; nodeIndexToIdStringMap[nodeIndex] = nodeIdString; @@ -300,6 +308,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt nodemesh::Builder *builder = new nodemesh::Builder; builder->setDeformThickness(deformThickness); builder->setDeformWidth(deformWidth); + builder->setCutRotation(cutRotation); for (const auto &node: modifier->nodes()) builder->addNode(node.position, node.radius, node.cutTemplate); diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp index 980c9c7d..0d8a4a36 100644 --- a/src/parttreewidget.cpp +++ b/src/parttreewidget.cpp @@ -916,7 +916,7 @@ void PartTreeWidget::partWrapStateChanged(QUuid partId) return; } PartWidget *widget = (PartWidget *)itemWidget(item->second, 0); - widget->updateWrapButton(); + widget->updateCutRotationButton(); } void PartTreeWidget::partColorStateChanged(QUuid partId) diff --git a/src/partwidget.cpp b/src/partwidget.cpp index 2d0f5f5c..cd42de3d 100644 --- a/src/partwidget.cpp +++ b/src/partwidget.cpp @@ -60,10 +60,10 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : m_colorButton->setSizePolicy(retainSizePolicy); initButton(m_colorButton); - m_wrapButton = new QPushButton; - m_wrapButton->setToolTip(tr("Toggle convex wrap")); - m_wrapButton->setSizePolicy(retainSizePolicy); - initButton(m_wrapButton); + m_cutButton = new QPushButton; + m_cutButton->setToolTip(tr("Rotation")); + m_cutButton->setSizePolicy(retainSizePolicy); + initButton(m_cutButton); m_previewWidget = new ModelWidget; m_previewWidget->setAttribute(Qt::WA_TransparentForMouseEvents); @@ -90,14 +90,14 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : int col = 0; toolsLayout->addWidget(m_lockButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_disableButton, row, col++, Qt::AlignBottom); - toolsLayout->addWidget(m_wrapButton, row, col++, Qt::AlignBottom); + toolsLayout->addWidget(m_xMirrorButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_colorButton, row, col++, Qt::AlignBottom); row++; col = 0; toolsLayout->addWidget(m_subdivButton, row, col++, Qt::AlignTop); - toolsLayout->addWidget(m_deformButton, row, col++, Qt::AlignTop); - toolsLayout->addWidget(m_xMirrorButton, row, col++, Qt::AlignTop); + toolsLayout->addWidget(m_cutButton, row, col++, Qt::AlignTop); toolsLayout->addWidget(m_roundButton, row, col++, Qt::AlignTop); + toolsLayout->addWidget(m_deformButton, row, col++, Qt::AlignTop); m_visibleButton->setContentsMargins(0, 0, 0, 0); @@ -143,7 +143,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : connect(this, &PartWidget::setPartDeformThickness, m_document, &Document::setPartDeformThickness); connect(this, &PartWidget::setPartDeformWidth, m_document, &Document::setPartDeformWidth); connect(this, &PartWidget::setPartRoundState, m_document, &Document::setPartRoundState); - connect(this, &PartWidget::setPartWrapState, m_document, &Document::setPartWrapState); + connect(this, &PartWidget::setPartCutRotation, m_document, &Document::setPartCutRotation); connect(this, &PartWidget::setPartColorState, m_document, &Document::setPartColorState); connect(this, &PartWidget::setPartMaterialId, m_document, &Document::setPartMaterialId); connect(this, &PartWidget::checkPart, m_document, &Document::checkPart); @@ -230,14 +230,13 @@ PartWidget::PartWidget(const Document *document, QUuid partId) : showColorSettingPopup(mapFromGlobal(QCursor::pos())); }); - connect(m_wrapButton, &QPushButton::clicked, [=]() { + connect(m_cutButton, &QPushButton::clicked, [=]() { const SkeletonPart *part = m_document->findPart(m_partId); if (!part) { qDebug() << "Part not found:" << m_partId; return; } - emit setPartWrapState(m_partId, !part->wrapped); - emit groupOperationAdded(); + showCutRotationSettingPopup(mapFromGlobal(QCursor::pos())); }); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); @@ -266,7 +265,7 @@ void PartWidget::updateAllButtons() updateDeformButton(); updateRoundButton(); updateColorButton(); - updateWrapButton(); + updateCutRotationButton(); } void PartWidget::updateCheckedState(bool checked) @@ -373,6 +372,52 @@ void PartWidget::showColorSettingPopup(const QPoint &pos) popupMenu.exec(mapToGlobal(pos)); } +void PartWidget::showCutRotationSettingPopup(const QPoint &pos) +{ + QMenu popupMenu; + + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Find part failed:" << m_partId; + return; + } + + QWidget *popup = new QWidget; + + FloatNumberWidget *rotationWidget = new FloatNumberWidget; + rotationWidget->setItemName(tr("Rotation")); + rotationWidget->setRange(-1, 1); + rotationWidget->setValue(part->cutRotation); + + connect(rotationWidget, &FloatNumberWidget::valueChanged, [=](float value) { + emit setPartCutRotation(m_partId, value); + emit groupOperationAdded(); + }); + + QPushButton *rotationEraser = new QPushButton(QChar(fa::eraser)); + initToolButton(rotationEraser); + + connect(rotationEraser, &QPushButton::clicked, [=]() { + rotationWidget->setValue(0.0); + emit groupOperationAdded(); + }); + + QVBoxLayout *layout = new QVBoxLayout; + QHBoxLayout *rotationLayout = new QHBoxLayout; + rotationLayout->addWidget(rotationEraser); + rotationLayout->addWidget(rotationWidget); + layout->addLayout(rotationLayout); + + popup->setLayout(layout); + + QWidgetAction action(this); + action.setDefaultWidget(popup); + + popupMenu.addAction(&action); + + popupMenu.exec(mapToGlobal(pos)); +} + void PartWidget::showDeformSettingPopup(const QPoint &pos) { QMenu popupMenu; @@ -567,17 +612,17 @@ void PartWidget::updateColorButton() updateButton(m_colorButton, QChar(fa::eyedropper), false); } -void PartWidget::updateWrapButton() +void PartWidget::updateCutRotationButton() { const SkeletonPart *part = m_document->findPart(m_partId); if (!part) { qDebug() << "Part not found:" << m_partId; return; } - if (part->wrapped) - updateButton(m_wrapButton, QChar(fa::cube), true); + if (part->cutRotationAdjusted()) + updateButton(m_cutButton, QChar(fa::spinner), true); else - updateButton(m_wrapButton, QChar(fa::cube), false); + updateButton(m_cutButton, QChar(fa::spinner), false); } void PartWidget::reload() diff --git a/src/partwidget.h b/src/partwidget.h index f6b63609..b51c419e 100644 --- a/src/partwidget.h +++ b/src/partwidget.h @@ -20,7 +20,7 @@ signals: void setPartDeformWidth(QUuid partId, float width); void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); - void setPartWrapState(QUuid partId, bool wrapped); + void setPartCutRotation(QUuid partId, float cutRotation); void setPartMaterialId(QUuid partId, QUuid materialId); void movePartUp(QUuid partId); void movePartDown(QUuid partId); @@ -43,7 +43,7 @@ public: void updateDeformButton(); void updateRoundButton(); void updateColorButton(); - void updateWrapButton(); + void updateCutRotationButton(); void updateCheckedState(bool checked); void updateUnnormalState(bool unnormal); static QSize preferredSize(); @@ -52,6 +52,7 @@ protected: void mouseDoubleClickEvent(QMouseEvent *event) override; public slots: void showDeformSettingPopup(const QPoint &pos); + void showCutRotationSettingPopup(const QPoint &pos); void showColorSettingPopup(const QPoint &pos); private: // need initialize const Document *m_document; @@ -68,7 +69,7 @@ private: QPushButton *m_deformButton; QPushButton *m_roundButton; QPushButton *m_colorButton; - QPushButton *m_wrapButton; + QPushButton *m_cutButton; QWidget *m_backgroundWidget; private: void initToolButton(QPushButton *button); diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp index edc36ebe..c1889f8b 100644 --- a/src/shortcuts.cpp +++ b/src/shortcuts.cpp @@ -45,5 +45,4 @@ void initShortCuts(QWidget *widget, SkeletonGraphicsWidget *graphicsWidget) defineKey(Qt::Key_M, &SkeletonGraphicsWidget::shortcutXmirrorOnOrOffSelectedPart); defineKey(Qt::Key_B, &SkeletonGraphicsWidget::shortcutSubdivedOrNotSelectedPart); defineKey(Qt::Key_U, &SkeletonGraphicsWidget::shortcutRoundEndOrNotSelectedPart); - defineKey(Qt::Key_W, &SkeletonGraphicsWidget::shortcutWrapOrNotSelectedPart); } diff --git a/src/skeletondocument.h b/src/skeletondocument.h index fa83124f..b1339f56 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -83,7 +83,7 @@ public: QUuid componentId; std::vector nodeIds; bool dirty; - bool wrapped; + float cutRotation; QUuid materialId; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), @@ -98,7 +98,7 @@ public: color(Theme::white), hasColor(false), dirty(true), - wrapped(false) + cutRotation(0.0) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -118,6 +118,14 @@ public: toWidth = 2; deformWidth = toWidth; } + void setCutRotation(float toRotation) + { + if (toRotation < -1) + toRotation = -1; + else if (toRotation > 1) + toRotation = 1; + cutRotation = toRotation; + } bool deformThicknessAdjusted() const { return fabs(deformThickness - 1.0) >= 0.01; @@ -130,6 +138,10 @@ public: { return deformThicknessAdjusted() || deformWidthAdjusted(); } + bool cutRotationAdjusted() const + { + return fabs(cutRotation - 0.0) >= 0.01; + } bool materialAdjusted() const { return !materialId.isNull(); @@ -151,7 +163,7 @@ public: rounded = other.rounded; color = other.color; hasColor = other.hasColor; - wrapped = other.wrapped; + cutRotation = other.cutRotation; componentId = other.componentId; dirty = other.dirty; materialId = other.materialId; diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 25fd44dc..fbfd8c2a 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -1682,16 +1682,6 @@ void SkeletonGraphicsWidget::shortcutRoundEndOrNotSelectedPart() } } -void SkeletonGraphicsWidget::shortcutWrapOrNotSelectedPart() -{ - if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { - const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); - bool partWrapped = part && part->wrapped; - emit setPartWrapState(m_lastCheckedPart, !partWrapped); - emit groupOperationAdded(); - } -} - bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) { if (event->key() == Qt::Key_Space) { diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 862f03f3..948a608b 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -507,7 +507,6 @@ public slots: void shortcutXmirrorOnOrOffSelectedPart(); void shortcutSubdivedOrNotSelectedPart(); void shortcutRoundEndOrNotSelectedPart(); - void shortcutWrapOrNotSelectedPart(); private slots: void turnaroundImageReady(); private: diff --git a/thirdparty/nodemesh/nodemesh/builder.cpp b/thirdparty/nodemesh/nodemesh/builder.cpp index 3505c4a7..bc5aed27 100644 --- a/thirdparty/nodemesh/nodemesh/builder.cpp +++ b/thirdparty/nodemesh/nodemesh/builder.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #define WRAP_STEP_BACK_FACTOR 0.1 // 0.1 ~ 0.9 #define WRAP_WELD_FACTOR 0.01 // Allowed distance: WELD_FACTOR * radius @@ -521,7 +522,19 @@ void Builder::makeCut(const QVector3D &position, auto uFactor = u * radius; auto vFactor = v * radius; for (const auto &t: cutTemplate) { - resultCut.push_back(position + uFactor * t.x() + vFactor * t.y()); + resultCut.push_back(uFactor * t.x() + vFactor * t.y()); + } + if (!qFuzzyIsNull(m_cutRotation)) { + float degree = m_cutRotation * 180; + QMatrix4x4 rotation; + rotation.rotate(degree, cutNormal); + baseNormal = rotation * baseNormal; + for (auto &positionOnCut: resultCut) { + positionOnCut = rotation * positionOnCut; + } + } + for (auto &positionOnCut: resultCut) { + positionOnCut += position; } } @@ -598,6 +611,11 @@ void Builder::setDeformWidth(float width) m_deformWidth = width; } +void Builder::setCutRotation(float cutRotation) +{ + m_cutRotation = cutRotation; +} + QVector3D Builder::calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor) { QVector3D revisedNormal = QVector3D::dotProduct(ray, deformNormal) < 0.0 ? -deformNormal : deformNormal; diff --git a/thirdparty/nodemesh/nodemesh/builder.h b/thirdparty/nodemesh/nodemesh/builder.h index 8f43a2f5..112d3356 100644 --- a/thirdparty/nodemesh/nodemesh/builder.h +++ b/thirdparty/nodemesh/nodemesh/builder.h @@ -16,6 +16,7 @@ public: size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex); void setDeformThickness(float thickness); void setDeformWidth(float width); + void setCutRotation(float cutRotation); const std::vector &generatedVertices(); const std::vector> &generatedFaces(); const std::vector &generatedVerticesSourceNodeIndices(); @@ -86,6 +87,7 @@ private: std::set m_swallowedNodes; float m_deformThickness = 1.0; float m_deformWidth = 1.0; + float m_cutRotation = 0.0; void sortNodeIndices(); void prepareNode(size_t nodeIndex);