Add stiffness setting for cloth simulation

master
Jeremy Hu 2020-01-09 23:30:46 +09:30
parent c4882dec1f
commit 44cd33d799
10 changed files with 133 additions and 19 deletions

View File

@ -752,6 +752,10 @@ Tips:
<source>Layer</source> <source>Layer</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Stiffness</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>PartWidget</name> <name>PartWidget</name>

View File

@ -55,16 +55,13 @@ private:
}; };
// System parameters // System parameters
namespace SystemParam { //namespace SystemParam {
static const int n = 61; // must be odd, n * n = n_vertices | 61 // 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.001f; // time step, smaller for better results | 0.008f = 0.016f/2
static const float h = 0.008f; // 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 r = w / (n - 1) * 1.05f; // spring rest legnth // static const float a = 0.993f; // damping, close to 1.0 | 0.993f
static const float k = 1.0f; // spring stiffness | 1.0f; // static const float g = 9.8f * m; // gravitational force | 9.8f
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 // Point - mesh collision node
class CgMeshCollisionNode : public CgPointNode { class CgMeshCollisionNode : public CgPointNode {
@ -136,6 +133,11 @@ ClothSimulator::~ClothSimulator()
delete m_meshCollisionNode; delete m_meshCollisionNode;
} }
void ClothSimulator::setStiffness(float stiffness)
{
m_stiffness = 1.0f + 5.0f * stiffness;
}
void ClothSimulator::convertMeshToCloth() void ClothSimulator::convertMeshToCloth()
{ {
m_clothPointSources.reserve(m_vertices.size()); m_clothPointSources.reserve(m_vertices.size());
@ -176,7 +178,7 @@ void ClothSimulator::getCurrentVertices(std::vector<QVector3D> *currentVertices)
size_t oldIndex = m_clothPointSources[newIndex]; size_t oldIndex = m_clothPointSources[newIndex];
auto offset = newIndex * 3; auto offset = newIndex * 3;
(*currentVertices)[oldIndex] = QVector3D(m_clothPointBuffer[offset + 0], (*currentVertices)[oldIndex] = QVector3D(m_clothPointBuffer[offset + 0],
m_clothPointBuffer[offset + 1], m_clothPointBuffer[offset + 1] + 0.01,
m_clothPointBuffer[offset + 2]); m_clothPointBuffer[offset + 2]);
} }
} }
@ -200,7 +202,12 @@ void ClothSimulator::create()
if (m_clothPointSources.empty()) if (m_clothPointSources.empty())
return; 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::EdgeList springList(m_clothSprings.size());
mass_spring_system::VectorXf restLengths(m_clothSprings.size()); mass_spring_system::VectorXf restLengths(m_clothSprings.size());
@ -210,13 +217,13 @@ void ClothSimulator::create()
const auto &source = m_clothSprings[i]; const auto &source = m_clothSprings[i];
springList[i] = mass_spring_system::Edge(source.first, source.second); 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(); 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, m_massSpringSystem = new mass_spring_system(m_clothPointSources.size(), m_clothSprings.size(), timeStep, springList, restLengths,
stiffnesses, masses, fext, SystemParam::a); stiffnesses, masses, fext, damping);
m_massSpringSolver = new MassSpringSolver(m_massSpringSystem, m_clothPointBuffer.data()); m_massSpringSolver = new MassSpringSolver(m_massSpringSystem, m_clothPointBuffer.data());

View File

@ -19,6 +19,7 @@ public:
const std::vector<QVector3D> &collisionVertices, const std::vector<QVector3D> &collisionVertices,
const std::vector<std::vector<size_t>> &collisionTriangles); const std::vector<std::vector<size_t>> &collisionTriangles);
~ClothSimulator(); ~ClothSimulator();
void setStiffness(float stiffness);
void create(); void create();
void step(); void step();
void getCurrentVertices(std::vector<QVector3D> *currentVertices); void getCurrentVertices(std::vector<QVector3D> *currentVertices);
@ -30,6 +31,7 @@ private:
std::vector<float> m_clothPointBuffer; std::vector<float> m_clothPointBuffer;
std::vector<size_t> m_clothPointSources; std::vector<size_t> m_clothPointSources;
std::vector<std::pair<size_t, size_t>> m_clothSprings; std::vector<std::pair<size_t, size_t>> m_clothSprings;
float m_stiffness = 1.0f;
mass_spring_system *m_massSpringSystem = nullptr; mass_spring_system *m_massSpringSystem = nullptr;
MassSpringSolver *m_massSpringSolver = nullptr; MassSpringSolver *m_massSpringSolver = nullptr;
CgRootNode *m_rootNode = nullptr; CgRootNode *m_rootNode = nullptr;

View File

@ -1203,6 +1203,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
component["polyCount"] = PolyCountToString(componentIt.second.polyCount); component["polyCount"] = PolyCountToString(componentIt.second.polyCount);
if (componentIt.second.layer != ComponentLayer::Body) if (componentIt.second.layer != ComponentLayer::Body)
component["layer"] = ComponentLayerToString(componentIt.second.layer); component["layer"] = ComponentLayerToString(componentIt.second.layer);
if (componentIt.second.clothStiffnessAdjusted())
component["clothStiffness"] = QString::number(componentIt.second.clothStiffness);
QStringList childIdList; QStringList childIdList;
for (const auto &childId: componentIt.second.childrenIds) { for (const auto &childId: componentIt.second.childrenIds) {
childIdList.append(childId.toString()); childIdList.append(childId.toString());
@ -1711,6 +1713,9 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
component.setSmoothSeam(smoothSeamIt->second.toFloat()); component.setSmoothSeam(smoothSeamIt->second.toFloat());
component.polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(componentKv.second, "polyCount").toUtf8().constData()); component.polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(componentKv.second, "polyCount").toUtf8().constData());
component.layer = ComponentLayerFromString(valueOfKeyInMapOrEmpty(componentKv.second, "layer").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; //qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name;
if ("partId" == linkDataType) { if ("partId" == linkDataType) {
QUuid partId = oldNewIdMap[QUuid(linkData)]; QUuid partId = oldNewIdMap[QUuid(linkData)];
@ -2514,6 +2519,20 @@ void Document::setComponentLayer(QUuid componentId, ComponentLayer layer)
emit skeletonChanged(); 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) void Document::createNewComponentAndMoveThisIn(QUuid componentId)
{ {
auto component = componentMap.find(componentId); auto component = componentMap.find(componentId);

View File

@ -69,6 +69,7 @@ public:
float smoothSeam = 0.0; float smoothSeam = 0.0;
PolyCount polyCount = PolyCount::Original; PolyCount polyCount = PolyCount::Original;
ComponentLayer layer = ComponentLayer::Body; ComponentLayer layer = ComponentLayer::Body;
float clothStiffness = 1.0f;
std::vector<QUuid> childrenIds; std::vector<QUuid> childrenIds;
QString linkData() const QString linkData() const
{ {
@ -188,6 +189,10 @@ public:
{ {
return smoothAllAdjusted() || smoothSeamAdjusted(); return smoothAllAdjusted() || smoothSeamAdjusted();
} }
bool clothStiffnessAdjusted() const
{
return fabs(clothStiffness - 1.0) >= 0.01;
}
private: private:
std::set<QUuid> m_childrenIdSet; std::set<QUuid> m_childrenIdSet;
}; };
@ -399,6 +404,7 @@ signals:
void componentSmoothSeamChanged(QUuid componentId); void componentSmoothSeamChanged(QUuid componentId);
void componentPolyCountChanged(QUuid componentId); void componentPolyCountChanged(QUuid componentId);
void componentLayerChanged(QUuid componentId); void componentLayerChanged(QUuid componentId);
void componentClothStiffnessChanged(QUuid componentId);
void nodeRemoved(QUuid nodeId); void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId); void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId); void nodeRadiusChanged(QUuid nodeId);
@ -646,6 +652,7 @@ public slots:
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam); void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
void setComponentPolyCount(QUuid componentId, PolyCount count); void setComponentPolyCount(QUuid componentId, PolyCount count);
void setComponentLayer(QUuid componentId, ComponentLayer layer); void setComponentLayer(QUuid componentId, ComponentLayer layer);
void setComponentClothStiffness(QUuid componentId, float stiffness);
void hideOtherComponents(QUuid componentId); void hideOtherComponents(QUuid componentId);
void lockOtherComponents(QUuid componentId); void lockOtherComponents(QUuid componentId);
void hideAllComponents(); void hideAllComponents();

View File

@ -1029,6 +1029,7 @@ DocumentWindow::DocumentWindow() :
connect(partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState); connect(partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState);
connect(partTreeWidget, &PartTreeWidget::setPartColorState, m_document, &Document::setPartColorState); connect(partTreeWidget, &PartTreeWidget::setPartColorState, m_document, &Document::setPartColorState);
connect(partTreeWidget, &PartTreeWidget::setComponentCombineMode, m_document, &Document::setComponentCombineMode); 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::setPartTarget, m_document, &Document::setPartTarget);
connect(partTreeWidget, &PartTreeWidget::setPartBase, m_document, &Document::setPartBase); connect(partTreeWidget, &PartTreeWidget::setPartBase, m_document, &Document::setPartBase);
connect(partTreeWidget, &PartTreeWidget::hideDescendantComponents, m_document, &Document::hideDescendantComponents); connect(partTreeWidget, &PartTreeWidget::hideDescendantComponents, m_document, &Document::hideDescendantComponents);

View File

@ -893,6 +893,16 @@ ComponentLayer MeshGenerator::componentLayer(const std::map<QString, QString> *c
return ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData()); return ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData());
} }
float MeshGenerator::componentClothStiffness(const std::map<QString, QString> *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 *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode)
{ {
MeshCombiner::Mesh *mesh = nullptr; MeshCombiner::Mesh *mesh = nullptr;
@ -1613,10 +1623,13 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
isotropicRemesh(uncombinedVertices, uncombinedFaces, uncombinedVertices, uncombinedFaces, 0.02f, 3); isotropicRemesh(uncombinedVertices, uncombinedFaces, uncombinedVertices, uncombinedFaces, 0.02f, 3);
std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap; std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap;
std::pair<QUuid, QUuid> defaultSource;
for (const auto &it: componentCache.outcomeNodeVertices) { for (const auto &it: componentCache.outcomeNodeVertices) {
if (!it.second.first.isNull())
defaultSource.first = it.second.first;
positionMap.insert({PositionKey(it.first), it.second}); positionMap.insert({PositionKey(it.first), it.second});
} }
std::vector<std::pair<QUuid, QUuid>> uncombinedVertexSources(uncombinedVertices.size()); std::vector<std::pair<QUuid, QUuid>> uncombinedVertexSources(uncombinedVertices.size(), defaultSource);
for (size_t i = 0; i < uncombinedVertices.size(); ++i) { for (size_t i = 0; i < uncombinedVertices.size(); ++i) {
auto findSource = positionMap.find(PositionKey(uncombinedVertices[i])); auto findSource = positionMap.find(PositionKey(uncombinedVertices[i]));
if (findSource == positionMap.end()) if (findSource == positionMap.end())
@ -1628,8 +1641,9 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
uncombinedFaces, uncombinedFaces,
m_clothCollisionVertices, m_clothCollisionVertices,
m_clothCollisionTriangles); m_clothCollisionTriangles);
clothSimulator.setStiffness(componentClothStiffness(component));
clothSimulator.create(); clothSimulator.create();
for (size_t i = 0; i < 30; ++i) for (size_t i = 0; i < 400; ++i)
clothSimulator.step(); clothSimulator.step();
clothSimulator.getCurrentVertices(&uncombinedVertices); clothSimulator.getCurrentVertices(&uncombinedVertices);

View File

@ -129,6 +129,7 @@ private:
MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true); MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true);
QString componentColorName(const std::map<QString, QString> *component); QString componentColorName(const std::map<QString, QString> *component);
ComponentLayer componentLayer(const std::map<QString, QString> *component); ComponentLayer componentLayer(const std::map<QString, QString> *component);
float componentClothStiffness(const std::map<QString, QString> *component);
void collectUncombinedComponent(const QString &componentIdString); void collectUncombinedComponent(const QString &componentIdString);
void collectClothComponent(const QString &componentIdString); void collectClothComponent(const QString &componentIdString);
void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate); void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate);

View File

@ -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) void PartTreeWidget::showContextMenu(const QPoint &pos)
{ {
const Component *component = nullptr; const Component *component = nullptr;
@ -315,6 +360,13 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
} }
QWidget *widget = new QWidget; QWidget *widget = new QWidget;
if (nullptr != component) { 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; QComboBox *componentLayerSelectBox = new QComboBox;
for (size_t i = 0; i < (size_t)ComponentLayer::Count; ++i) { for (size_t i = 0; i < (size_t)ComponentLayer::Count; ++i) {
ComponentLayer layer = (ComponentLayer)i; ComponentLayer layer = (ComponentLayer)i;
@ -322,9 +374,14 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
} }
componentLayerSelectBox->setCurrentIndex((int)component->layer); componentLayerSelectBox->setCurrentIndex((int)component->layer);
connect(componentLayerSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) { connect(componentLayerSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
clothSettingButton->setVisible(ComponentLayer::Cloth == (ComponentLayer)index);
emit setComponentLayer(component->id, (ComponentLayer)index); emit setComponentLayer(component->id, (ComponentLayer)index);
emit groupOperationAdded(); emit groupOperationAdded();
}); });
QHBoxLayout *componentLayerLayout = new QHBoxLayout;
componentLayerLayout->addWidget(componentLayerSelectBox);
componentLayerLayout->addWidget(clothSettingButton);
componentLayerLayout->setStretch(0, 1);
QComboBox *combineModeSelectBox = new QComboBox; QComboBox *combineModeSelectBox = new QComboBox;
for (size_t i = 0; i < (size_t)CombineMode::Count; ++i) { for (size_t i = 0; i < (size_t)CombineMode::Count; ++i) {
@ -379,7 +436,7 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
if (nullptr != partTargetSelectBox) if (nullptr != partTargetSelectBox)
componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox); componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox);
componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox); componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox);
componentSettingsLayout->addRow(tr("Layer"), componentLayerSelectBox); componentSettingsLayout->addRow(tr("Layer"), componentLayerLayout);
QVBoxLayout *newLayout = new QVBoxLayout; QVBoxLayout *newLayout = new QVBoxLayout;
newLayout->addLayout(layout); newLayout->addLayout(layout);

View File

@ -41,6 +41,7 @@ signals:
void setPartVisibleState(QUuid partId, bool visible); void setPartVisibleState(QUuid partId, bool visible);
void setPartColorState(QUuid partId, bool hasColor, QColor color); void setPartColorState(QUuid partId, bool hasColor, QColor color);
void setComponentCombineMode(QUuid componentId, CombineMode combineMode); void setComponentCombineMode(QUuid componentId, CombineMode combineMode);
void setComponentClothStiffness(QUuid componentId, float clothStiffness);
void hideDescendantComponents(QUuid componentId); void hideDescendantComponents(QUuid componentId);
void showDescendantComponents(QUuid componentId); void showDescendantComponents(QUuid componentId);
void lockDescendantComponents(QUuid componentId); void lockDescendantComponents(QUuid componentId);
@ -83,6 +84,7 @@ public slots:
void groupCollapsed(QTreeWidgetItem *item); void groupCollapsed(QTreeWidgetItem *item);
void removeAllContent(); void removeAllContent();
void showContextMenu(const QPoint &pos); void showContextMenu(const QPoint &pos);
void showClothSettingMenu(const QPoint &pos, const QUuid &componentId);
protected: protected:
QSize sizeHint() const override; QSize sizeHint() const override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;