diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index be693a10..623adccd 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -752,6 +752,10 @@ Tips: Layer + + Stiffness + + PartWidget diff --git a/src/clothsimulator.cpp b/src/clothsimulator.cpp index 26b7aae1..77f8a0f4 100644 --- a/src/clothsimulator.cpp +++ b/src/clothsimulator.cpp @@ -55,16 +55,13 @@ private: }; // System parameters -namespace SystemParam { - static const int n = 61; // must be odd, n * n = n_vertices | 61 - static const float w = 2.0f; // width | 2.0f - static const float h = 0.008f; // time step, smaller for better results | 0.008f = 0.016f/2 - static const float r = w / (n - 1) * 1.05f; // spring rest legnth - static const float k = 1.0f; // spring stiffness | 1.0f; - static const float m = 0.25f / (n * n); // point mass | 0.25f - static const float a = 0.993f; // damping, close to 1.0 | 0.993f - static const float g = 9.8f * m; // gravitational force | 9.8f -} +//namespace SystemParam { +// static const int n = 61; // must be odd, n * n = n_vertices | 61 +// static const float h = 0.001f; // time step, smaller for better results | 0.008f = 0.016f/2 +// static const float m = 0.25f / (n * n); // point mass | 0.25f +// static const float a = 0.993f; // damping, close to 1.0 | 0.993f +// static const float g = 9.8f * m; // gravitational force | 9.8f +//} // Point - mesh collision node class CgMeshCollisionNode : public CgPointNode { @@ -136,6 +133,11 @@ ClothSimulator::~ClothSimulator() delete m_meshCollisionNode; } +void ClothSimulator::setStiffness(float stiffness) +{ + m_stiffness = 1.0f + 5.0f * stiffness; +} + void ClothSimulator::convertMeshToCloth() { m_clothPointSources.reserve(m_vertices.size()); @@ -176,7 +178,7 @@ void ClothSimulator::getCurrentVertices(std::vector *currentVertices) size_t oldIndex = m_clothPointSources[newIndex]; auto offset = newIndex * 3; (*currentVertices)[oldIndex] = QVector3D(m_clothPointBuffer[offset + 0], - m_clothPointBuffer[offset + 1], + m_clothPointBuffer[offset + 1] + 0.01, m_clothPointBuffer[offset + 2]); } } @@ -200,7 +202,12 @@ void ClothSimulator::create() if (m_clothPointSources.empty()) return; - mass_spring_system::VectorXf masses(SystemParam::m * mass_spring_system::VectorXf::Ones((unsigned int)m_clothSprings.size())); + float mass = 0.25f / m_clothPointSources.size(); + float gravitationalForce = 9.8f * mass; + float damping = 0.993f; // damping, close to 1.0 | 0.993f; + float timeStep = 0.001f; //smaller for better results | 0.008f = 0.016f/2; + + mass_spring_system::VectorXf masses(mass * mass_spring_system::VectorXf::Ones((unsigned int)m_clothSprings.size())); mass_spring_system::EdgeList springList(m_clothSprings.size()); mass_spring_system::VectorXf restLengths(m_clothSprings.size()); @@ -210,13 +217,13 @@ void ClothSimulator::create() const auto &source = m_clothSprings[i]; springList[i] = mass_spring_system::Edge(source.first, source.second); restLengths[i] = (m_vertices[m_clothPointSources[source.first]] - m_vertices[m_clothPointSources[source.second]]).length(); - stiffnesses[i] = SystemParam::k; + stiffnesses[i] = m_stiffness; } - mass_spring_system::VectorXf fext = Eigen::Vector3f(0, -SystemParam::g, 0).replicate(m_clothPointSources.size(), 1); + mass_spring_system::VectorXf fext = Eigen::Vector3f(0, -gravitationalForce, 0).replicate(m_clothPointSources.size(), 1); - m_massSpringSystem = new mass_spring_system(m_clothPointSources.size(), m_clothSprings.size(), SystemParam::h, springList, restLengths, - stiffnesses, masses, fext, SystemParam::a); + m_massSpringSystem = new mass_spring_system(m_clothPointSources.size(), m_clothSprings.size(), timeStep, springList, restLengths, + stiffnesses, masses, fext, damping); m_massSpringSolver = new MassSpringSolver(m_massSpringSystem, m_clothPointBuffer.data()); diff --git a/src/clothsimulator.h b/src/clothsimulator.h index 6c238fb4..c7b7c412 100644 --- a/src/clothsimulator.h +++ b/src/clothsimulator.h @@ -19,6 +19,7 @@ public: const std::vector &collisionVertices, const std::vector> &collisionTriangles); ~ClothSimulator(); + void setStiffness(float stiffness); void create(); void step(); void getCurrentVertices(std::vector *currentVertices); @@ -30,6 +31,7 @@ private: std::vector m_clothPointBuffer; std::vector m_clothPointSources; std::vector> m_clothSprings; + float m_stiffness = 1.0f; mass_spring_system *m_massSpringSystem = nullptr; MassSpringSolver *m_massSpringSolver = nullptr; CgRootNode *m_rootNode = nullptr; diff --git a/src/document.cpp b/src/document.cpp index 6837e389..8cfe5bc5 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -1203,6 +1203,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId component["polyCount"] = PolyCountToString(componentIt.second.polyCount); if (componentIt.second.layer != ComponentLayer::Body) component["layer"] = ComponentLayerToString(componentIt.second.layer); + if (componentIt.second.clothStiffnessAdjusted()) + component["clothStiffness"] = QString::number(componentIt.second.clothStiffness); QStringList childIdList; for (const auto &childId: componentIt.second.childrenIds) { childIdList.append(childId.toString()); @@ -1711,6 +1713,9 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) component.setSmoothSeam(smoothSeamIt->second.toFloat()); component.polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(componentKv.second, "polyCount").toUtf8().constData()); component.layer = ComponentLayerFromString(valueOfKeyInMapOrEmpty(componentKv.second, "layer").toUtf8().constData()); + auto findClothStiffness = componentKv.second.find("clothStiffness"); + if (findClothStiffness != componentKv.second.end()) + component.clothStiffness = findClothStiffness->second.toFloat(); //qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name; if ("partId" == linkDataType) { QUuid partId = oldNewIdMap[QUuid(linkData)]; @@ -2514,6 +2519,20 @@ void Document::setComponentLayer(QUuid componentId, ComponentLayer layer) emit skeletonChanged(); } +void Document::setComponentClothStiffness(QUuid componentId, float stiffness) +{ + Component *component = (Component *)findComponent(componentId); + if (nullptr == component) + return; + if (qFuzzyCompare(component->clothStiffness, stiffness)) + return; + + component->clothStiffness = stiffness; + component->dirty = true; + emit componentClothStiffnessChanged(componentId); + emit skeletonChanged(); +} + void Document::createNewComponentAndMoveThisIn(QUuid componentId) { auto component = componentMap.find(componentId); diff --git a/src/document.h b/src/document.h index 7a40a94f..21ea0c5a 100644 --- a/src/document.h +++ b/src/document.h @@ -69,6 +69,7 @@ public: float smoothSeam = 0.0; PolyCount polyCount = PolyCount::Original; ComponentLayer layer = ComponentLayer::Body; + float clothStiffness = 1.0f; std::vector childrenIds; QString linkData() const { @@ -188,6 +189,10 @@ public: { return smoothAllAdjusted() || smoothSeamAdjusted(); } + bool clothStiffnessAdjusted() const + { + return fabs(clothStiffness - 1.0) >= 0.01; + } private: std::set m_childrenIdSet; }; @@ -399,6 +404,7 @@ signals: void componentSmoothSeamChanged(QUuid componentId); void componentPolyCountChanged(QUuid componentId); void componentLayerChanged(QUuid componentId); + void componentClothStiffnessChanged(QUuid componentId); void nodeRemoved(QUuid nodeId); void edgeRemoved(QUuid edgeId); void nodeRadiusChanged(QUuid nodeId); @@ -646,6 +652,7 @@ public slots: void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam); void setComponentPolyCount(QUuid componentId, PolyCount count); void setComponentLayer(QUuid componentId, ComponentLayer layer); + void setComponentClothStiffness(QUuid componentId, float stiffness); void hideOtherComponents(QUuid componentId); void lockOtherComponents(QUuid componentId); void hideAllComponents(); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index bbc3f31d..b07d75cb 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -1029,6 +1029,7 @@ DocumentWindow::DocumentWindow() : connect(partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState); connect(partTreeWidget, &PartTreeWidget::setPartColorState, m_document, &Document::setPartColorState); connect(partTreeWidget, &PartTreeWidget::setComponentCombineMode, m_document, &Document::setComponentCombineMode); + connect(partTreeWidget, &PartTreeWidget::setComponentClothStiffness, m_document, &Document::setComponentClothStiffness); connect(partTreeWidget, &PartTreeWidget::setPartTarget, m_document, &Document::setPartTarget); connect(partTreeWidget, &PartTreeWidget::setPartBase, m_document, &Document::setPartBase); connect(partTreeWidget, &PartTreeWidget::hideDescendantComponents, m_document, &Document::hideDescendantComponents); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index fc32c8e9..97d381f3 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -893,6 +893,16 @@ ComponentLayer MeshGenerator::componentLayer(const std::map *c return ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData()); } +float MeshGenerator::componentClothStiffness(const std::map *component) +{ + if (nullptr == component) + return 1.0f; + auto findClothStiffness = component->find("clothStiffness"); + if (findClothStiffness == component->end()) + return 1.0f; + return findClothStiffness->second.toFloat(); +} + MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode) { MeshCombiner::Mesh *mesh = nullptr; @@ -1613,10 +1623,13 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString) isotropicRemesh(uncombinedVertices, uncombinedFaces, uncombinedVertices, uncombinedFaces, 0.02f, 3); std::map> positionMap; + std::pair defaultSource; for (const auto &it: componentCache.outcomeNodeVertices) { + if (!it.second.first.isNull()) + defaultSource.first = it.second.first; positionMap.insert({PositionKey(it.first), it.second}); } - std::vector> uncombinedVertexSources(uncombinedVertices.size()); + std::vector> uncombinedVertexSources(uncombinedVertices.size(), defaultSource); for (size_t i = 0; i < uncombinedVertices.size(); ++i) { auto findSource = positionMap.find(PositionKey(uncombinedVertices[i])); if (findSource == positionMap.end()) @@ -1628,8 +1641,9 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString) uncombinedFaces, m_clothCollisionVertices, m_clothCollisionTriangles); + clothSimulator.setStiffness(componentClothStiffness(component)); clothSimulator.create(); - for (size_t i = 0; i < 30; ++i) + for (size_t i = 0; i < 400; ++i) clothSimulator.step(); clothSimulator.getCurrentVertices(&uncombinedVertices); diff --git a/src/meshgenerator.h b/src/meshgenerator.h index 882f966d..d5eda2b0 100644 --- a/src/meshgenerator.h +++ b/src/meshgenerator.h @@ -129,6 +129,7 @@ private: MeshCombiner::Mesh *combineMultipleMeshes(const std::vector> &multipleMeshes, bool recombine=true); QString componentColorName(const std::map *component); ComponentLayer componentLayer(const std::map *component); + float componentClothStiffness(const std::map *component); void collectUncombinedComponent(const QString &componentIdString); void collectClothComponent(const QString &componentIdString); void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector &cutTemplate); diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp index a61c69bd..52658ac8 100644 --- a/src/parttreewidget.cpp +++ b/src/parttreewidget.cpp @@ -214,6 +214,51 @@ void PartTreeWidget::mousePressEvent(QMouseEvent *event) } } +void PartTreeWidget::showClothSettingMenu(const QPoint &pos, const QUuid &componentId) +{ + const Component *component = nullptr; + + if (componentId.isNull()) + return; + + component = m_document->findComponent(componentId); + if (nullptr == component) + return; + + QMenu popupMenu; + + QWidget *popup = new QWidget; + + FloatNumberWidget *clothStiffnessWidget = new FloatNumberWidget; + clothStiffnessWidget->setItemName(tr("Stiffness")); + clothStiffnessWidget->setRange(0.0f, 1.0f); + clothStiffnessWidget->setValue(component->clothStiffness); + + connect(clothStiffnessWidget, &FloatNumberWidget::valueChanged, [=](float value) { + emit setComponentClothStiffness(componentId, value); + emit groupOperationAdded(); + }); + + QPushButton *clothStiffnessEraser = new QPushButton(QChar(fa::eraser)); + Theme::initAwesomeToolButton(clothStiffnessEraser); + + QHBoxLayout *clothStiffnessLayout = new QHBoxLayout; + clothStiffnessLayout->addWidget(clothStiffnessEraser); + clothStiffnessLayout->addWidget(clothStiffnessWidget); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(clothStiffnessLayout); + + popup->setLayout(mainLayout); + + QWidgetAction action(this); + action.setDefaultWidget(popup); + + popupMenu.addAction(&action); + + popupMenu.exec(mapToGlobal(pos)); +} + void PartTreeWidget::showContextMenu(const QPoint &pos) { const Component *component = nullptr; @@ -315,6 +360,13 @@ void PartTreeWidget::showContextMenu(const QPoint &pos) } QWidget *widget = new QWidget; if (nullptr != component) { + QPushButton *clothSettingButton = new QPushButton(); + connect(clothSettingButton, &QPushButton::clicked, this, [=]() { + showClothSettingMenu(mapFromGlobal(QCursor::pos()), component->id); + }); + clothSettingButton->setIcon(Theme::awesome()->icon(fa::gear)); + if (ComponentLayer::Cloth != component->layer) + clothSettingButton->hide(); QComboBox *componentLayerSelectBox = new QComboBox; for (size_t i = 0; i < (size_t)ComponentLayer::Count; ++i) { ComponentLayer layer = (ComponentLayer)i; @@ -322,9 +374,14 @@ void PartTreeWidget::showContextMenu(const QPoint &pos) } componentLayerSelectBox->setCurrentIndex((int)component->layer); connect(componentLayerSelectBox, static_cast(&QComboBox::currentIndexChanged), this, [=](int index) { + clothSettingButton->setVisible(ComponentLayer::Cloth == (ComponentLayer)index); emit setComponentLayer(component->id, (ComponentLayer)index); emit groupOperationAdded(); }); + QHBoxLayout *componentLayerLayout = new QHBoxLayout; + componentLayerLayout->addWidget(componentLayerSelectBox); + componentLayerLayout->addWidget(clothSettingButton); + componentLayerLayout->setStretch(0, 1); QComboBox *combineModeSelectBox = new QComboBox; for (size_t i = 0; i < (size_t)CombineMode::Count; ++i) { @@ -379,7 +436,7 @@ void PartTreeWidget::showContextMenu(const QPoint &pos) if (nullptr != partTargetSelectBox) componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox); componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox); - componentSettingsLayout->addRow(tr("Layer"), componentLayerSelectBox); + componentSettingsLayout->addRow(tr("Layer"), componentLayerLayout); QVBoxLayout *newLayout = new QVBoxLayout; newLayout->addLayout(layout); diff --git a/src/parttreewidget.h b/src/parttreewidget.h index 72aef4b9..e26940ed 100644 --- a/src/parttreewidget.h +++ b/src/parttreewidget.h @@ -41,6 +41,7 @@ signals: void setPartVisibleState(QUuid partId, bool visible); void setPartColorState(QUuid partId, bool hasColor, QColor color); void setComponentCombineMode(QUuid componentId, CombineMode combineMode); + void setComponentClothStiffness(QUuid componentId, float clothStiffness); void hideDescendantComponents(QUuid componentId); void showDescendantComponents(QUuid componentId); void lockDescendantComponents(QUuid componentId); @@ -83,6 +84,7 @@ public slots: void groupCollapsed(QTreeWidgetItem *item); void removeAllContent(); void showContextMenu(const QPoint &pos); + void showClothSettingMenu(const QPoint &pos, const QUuid &componentId); protected: QSize sizeHint() const override; void mousePressEvent(QMouseEvent *event) override;