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:
层
+
+
+
+
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;