Add force and offset settings for cloth simulation
parent
8ffef700c4
commit
89d49d488d
|
@ -491,6 +491,12 @@ HEADERS += src/componentlayer.h
|
||||||
SOURCES += src/isotropicremesh.cpp
|
SOURCES += src/isotropicremesh.cpp
|
||||||
HEADERS += src/isotropicremesh.h
|
HEADERS += src/isotropicremesh.h
|
||||||
|
|
||||||
|
SOURCES += src/clothforce.cpp
|
||||||
|
HEADERS += src/clothforce.h
|
||||||
|
|
||||||
|
SOURCES += src/projectfacestonodes.cpp
|
||||||
|
HEADERS += src/projectfacestonodes.h
|
||||||
|
|
||||||
SOURCES += src/main.cpp
|
SOURCES += src/main.cpp
|
||||||
|
|
||||||
HEADERS += src/version.h
|
HEADERS += src/version.h
|
||||||
|
|
|
@ -756,6 +756,14 @@ Tips:
|
||||||
<source>Stiffness</source>
|
<source>Stiffness</source>
|
||||||
<translation>硬度</translation>
|
<translation>硬度</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Force</source>
|
||||||
|
<translation>力</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Offset</source>
|
||||||
|
<translation>偏移</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>PartWidget</name>
|
<name>PartWidget</name>
|
||||||
|
@ -1086,6 +1094,14 @@ Tips:
|
||||||
<source>Cloth</source>
|
<source>Cloth</source>
|
||||||
<translation>衣服</translation>
|
<translation>衣服</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Gravitational</source>
|
||||||
|
<translation>重力</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Centripetal</source>
|
||||||
|
<translation>向心力</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>RigWidget</name>
|
<name>RigWidget</name>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include <QObject>
|
||||||
|
#include "clothforce.h"
|
||||||
|
|
||||||
|
IMPL_ClothForceFromString
|
||||||
|
IMPL_ClothForceToString
|
||||||
|
IMPL_ClothForceToDispName
|
|
@ -0,0 +1,49 @@
|
||||||
|
#ifndef DUST3D_CLOTH_FORCE_H
|
||||||
|
#define DUST3D_CLOTH_FORCE_H
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
enum class ClothForce
|
||||||
|
{
|
||||||
|
Gravitational = 0,
|
||||||
|
Centripetal,
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
ClothForce ClothForceFromString(const char *forceString);
|
||||||
|
#define IMPL_ClothForceFromString \
|
||||||
|
ClothForce ClothForceFromString(const char *forceString) \
|
||||||
|
{ \
|
||||||
|
QString force = forceString; \
|
||||||
|
if (force == "Gravitational") \
|
||||||
|
return ClothForce::Gravitational; \
|
||||||
|
if (force == "Centripetal") \
|
||||||
|
return ClothForce::Centripetal; \
|
||||||
|
return ClothForce::Gravitational; \
|
||||||
|
}
|
||||||
|
const char *ClothForceToString(ClothForce force);
|
||||||
|
#define IMPL_ClothForceToString \
|
||||||
|
const char *ClothForceToString(ClothForce force) \
|
||||||
|
{ \
|
||||||
|
switch (force) { \
|
||||||
|
case ClothForce::Gravitational: \
|
||||||
|
return "Gravitational"; \
|
||||||
|
case ClothForce::Centripetal: \
|
||||||
|
return "Centripetal"; \
|
||||||
|
default: \
|
||||||
|
return "Gravitational"; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
QString ClothForceToDispName(ClothForce force);
|
||||||
|
#define IMPL_ClothForceToDispName \
|
||||||
|
QString ClothForceToDispName(ClothForce force) \
|
||||||
|
{ \
|
||||||
|
switch (force) { \
|
||||||
|
case ClothForce::Gravitational: \
|
||||||
|
return QObject::tr("Gravitational"); \
|
||||||
|
case ClothForce::Centripetal: \
|
||||||
|
return QObject::tr("Centripetal"); \
|
||||||
|
default: \
|
||||||
|
return QObject::tr("Gravitational"); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -112,7 +112,8 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fixPoints(CgPointFixNode *fixNode) {
|
void fixPoints(CgPointFixNode *fixNode)
|
||||||
|
{
|
||||||
for (unsigned int i = 0; i < system->n_points; i++) {
|
for (unsigned int i = 0; i < system->n_points; i++) {
|
||||||
auto offset = 3 * i;
|
auto offset = 3 * i;
|
||||||
Point point(vbuff[offset + 0],
|
Point point(vbuff[offset + 0],
|
||||||
|
@ -124,16 +125,32 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void collectErrorPoints(std::vector<size_t> *points)
|
||||||
|
{
|
||||||
|
for (unsigned int i = 0; i < system->n_points; i++) {
|
||||||
|
auto offset = 3 * i;
|
||||||
|
Point point(vbuff[offset + 0],
|
||||||
|
vbuff[offset + 1],
|
||||||
|
vbuff[offset + 2]);
|
||||||
|
if (nullptr != m_insideTester &&
|
||||||
|
(*m_insideTester)(point) != CGAL::ON_UNBOUNDED_SIDE) {
|
||||||
|
points->push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ClothSimulator::ClothSimulator(const std::vector<QVector3D> &vertices,
|
ClothSimulator::ClothSimulator(const std::vector<QVector3D> &vertices,
|
||||||
const std::vector<std::vector<size_t>> &faces,
|
const std::vector<std::vector<size_t>> &faces,
|
||||||
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,
|
||||||
|
const std::vector<QVector3D> &externalForces) :
|
||||||
m_vertices(vertices),
|
m_vertices(vertices),
|
||||||
m_faces(faces),
|
m_faces(faces),
|
||||||
m_collisionVertices(collisionVertices),
|
m_collisionVertices(collisionVertices),
|
||||||
m_collisionTriangles(collisionTriangles)
|
m_collisionTriangles(collisionTriangles),
|
||||||
|
m_externalForces(externalForces)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +209,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] + 0.01,
|
m_clothPointBuffer[offset + 1],
|
||||||
m_clothPointBuffer[offset + 2]);
|
m_clothPointBuffer[offset + 2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,11 +247,18 @@ void ClothSimulator::create()
|
||||||
for (size_t i = 0; i < m_clothSprings.size(); ++i) {
|
for (size_t i = 0; i < m_clothSprings.size(); ++i) {
|
||||||
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() * 0.8;
|
||||||
stiffnesses[i] = m_stiffness;
|
stiffnesses[i] = m_stiffness;
|
||||||
}
|
}
|
||||||
|
|
||||||
mass_spring_system::VectorXf fext = Eigen::Vector3f(0, -gravitationalForce, 0).replicate(m_clothPointSources.size(), 1);
|
mass_spring_system::VectorXf fext(m_clothPointSources.size() * 3);
|
||||||
|
for (size_t i = 0; i < m_clothPointSources.size(); ++i) {
|
||||||
|
const auto &externalForce = m_externalForces[i] * gravitationalForce;
|
||||||
|
auto offset = i * 3;
|
||||||
|
fext[offset + 0] = externalForce.x();
|
||||||
|
fext[offset + 1] = externalForce.y();
|
||||||
|
fext[offset + 2] = externalForce.z();
|
||||||
|
}
|
||||||
|
|
||||||
m_massSpringSystem = new mass_spring_system(m_clothPointSources.size(), m_clothSprings.size(), timeStep, springList, restLengths,
|
m_massSpringSystem = new mass_spring_system(m_clothPointSources.size(), m_clothSprings.size(), timeStep, springList, restLengths,
|
||||||
stiffnesses, masses, fext, damping);
|
stiffnesses, masses, fext, damping);
|
||||||
|
|
|
@ -18,7 +18,8 @@ public:
|
||||||
ClothSimulator(const std::vector<QVector3D> &vertices,
|
ClothSimulator(const std::vector<QVector3D> &vertices,
|
||||||
const std::vector<std::vector<size_t>> &faces,
|
const std::vector<std::vector<size_t>> &faces,
|
||||||
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,
|
||||||
|
const std::vector<QVector3D> &externalForces);
|
||||||
~ClothSimulator();
|
~ClothSimulator();
|
||||||
void setStiffness(float stiffness);
|
void setStiffness(float stiffness);
|
||||||
void create();
|
void create();
|
||||||
|
@ -29,10 +30,12 @@ private:
|
||||||
std::vector<std::vector<size_t>> m_faces;
|
std::vector<std::vector<size_t>> m_faces;
|
||||||
std::vector<QVector3D> m_collisionVertices;
|
std::vector<QVector3D> m_collisionVertices;
|
||||||
std::vector<std::vector<size_t>> m_collisionTriangles;
|
std::vector<std::vector<size_t>> m_collisionTriangles;
|
||||||
|
std::vector<QVector3D> m_externalForces;
|
||||||
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;
|
float m_stiffness = 1.0f;
|
||||||
|
QVector3D m_offset;
|
||||||
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;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "contourtopartconverter.h"
|
#include "contourtopartconverter.h"
|
||||||
|
|
||||||
unsigned long Document::m_maxSnapshot = 1000;
|
unsigned long Document::m_maxSnapshot = 1000;
|
||||||
|
const float Component::defaultStiffness = 0.5f;
|
||||||
|
|
||||||
Document::Document() :
|
Document::Document() :
|
||||||
SkeletonDocument(),
|
SkeletonDocument(),
|
||||||
|
@ -1205,6 +1206,10 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
|
||||||
component["layer"] = ComponentLayerToString(componentIt.second.layer);
|
component["layer"] = ComponentLayerToString(componentIt.second.layer);
|
||||||
if (componentIt.second.clothStiffnessAdjusted())
|
if (componentIt.second.clothStiffnessAdjusted())
|
||||||
component["clothStiffness"] = QString::number(componentIt.second.clothStiffness);
|
component["clothStiffness"] = QString::number(componentIt.second.clothStiffness);
|
||||||
|
if (componentIt.second.clothForceAdjusted())
|
||||||
|
component["clothForce"] = ClothForceToString(componentIt.second.clothForce);
|
||||||
|
if (componentIt.second.clothOffsetAdjusted())
|
||||||
|
component["clothOffset"] = QString::number(componentIt.second.clothOffset);
|
||||||
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());
|
||||||
|
@ -1716,6 +1721,12 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
||||||
auto findClothStiffness = componentKv.second.find("clothStiffness");
|
auto findClothStiffness = componentKv.second.find("clothStiffness");
|
||||||
if (findClothStiffness != componentKv.second.end())
|
if (findClothStiffness != componentKv.second.end())
|
||||||
component.clothStiffness = findClothStiffness->second.toFloat();
|
component.clothStiffness = findClothStiffness->second.toFloat();
|
||||||
|
auto findClothForce = componentKv.second.find("clothForce");
|
||||||
|
if (findClothForce != componentKv.second.end())
|
||||||
|
component.clothForce = ClothForceFromString(valueOfKeyInMapOrEmpty(componentKv.second, "clothForce").toUtf8().constData());
|
||||||
|
auto findClothOffset = componentKv.second.find("clothOffset");
|
||||||
|
if (findClothOffset != componentKv.second.end())
|
||||||
|
component.clothOffset = valueOfKeyInMapOrEmpty(componentKv.second, "clothOffset").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)];
|
||||||
|
@ -2533,6 +2544,34 @@ void Document::setComponentClothStiffness(QUuid componentId, float stiffness)
|
||||||
emit skeletonChanged();
|
emit skeletonChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Document::setComponentClothForce(QUuid componentId, ClothForce force)
|
||||||
|
{
|
||||||
|
Component *component = (Component *)findComponent(componentId);
|
||||||
|
if (nullptr == component)
|
||||||
|
return;
|
||||||
|
if (component->clothForce == force)
|
||||||
|
return;
|
||||||
|
|
||||||
|
component->clothForce = force;
|
||||||
|
component->dirty = true;
|
||||||
|
emit componentClothForceChanged(componentId);
|
||||||
|
emit skeletonChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Document::setComponentClothOffset(QUuid componentId, float offset)
|
||||||
|
{
|
||||||
|
Component *component = (Component *)findComponent(componentId);
|
||||||
|
if (nullptr == component)
|
||||||
|
return;
|
||||||
|
if (qFuzzyCompare(component->clothOffset, offset))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component->clothOffset = offset;
|
||||||
|
component->dirty = true;
|
||||||
|
emit componentClothOffsetChanged(componentId);
|
||||||
|
emit skeletonChanged();
|
||||||
|
}
|
||||||
|
|
||||||
void Document::createNewComponentAndMoveThisIn(QUuid componentId)
|
void Document::createNewComponentAndMoveThisIn(QUuid componentId)
|
||||||
{
|
{
|
||||||
auto component = componentMap.find(componentId);
|
auto component = componentMap.find(componentId);
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include "paintmode.h"
|
#include "paintmode.h"
|
||||||
#include "proceduralanimation.h"
|
#include "proceduralanimation.h"
|
||||||
#include "componentlayer.h"
|
#include "componentlayer.h"
|
||||||
|
#include "clothforce.h"
|
||||||
|
|
||||||
class MaterialPreviewsGenerator;
|
class MaterialPreviewsGenerator;
|
||||||
class MotionsGenerator;
|
class MotionsGenerator;
|
||||||
|
@ -46,6 +47,7 @@ public:
|
||||||
class Component
|
class Component
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static const float defaultStiffness;
|
||||||
Component()
|
Component()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -69,7 +71,9 @@ 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;
|
float clothStiffness = defaultStiffness;
|
||||||
|
ClothForce clothForce = ClothForce::Gravitational;
|
||||||
|
float clothOffset = 0.0f;
|
||||||
std::vector<QUuid> childrenIds;
|
std::vector<QUuid> childrenIds;
|
||||||
QString linkData() const
|
QString linkData() const
|
||||||
{
|
{
|
||||||
|
@ -191,7 +195,15 @@ public:
|
||||||
}
|
}
|
||||||
bool clothStiffnessAdjusted() const
|
bool clothStiffnessAdjusted() const
|
||||||
{
|
{
|
||||||
return fabs(clothStiffness - 1.0) >= 0.01;
|
return fabs(clothStiffness - Component::defaultStiffness) >= 0.01;
|
||||||
|
}
|
||||||
|
bool clothForceAdjusted() const
|
||||||
|
{
|
||||||
|
return ClothForce::Gravitational != clothForce;
|
||||||
|
}
|
||||||
|
bool clothOffsetAdjusted() const
|
||||||
|
{
|
||||||
|
return fabs(clothOffset - 0.0) >= 0.01;
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
std::set<QUuid> m_childrenIdSet;
|
std::set<QUuid> m_childrenIdSet;
|
||||||
|
@ -405,6 +417,8 @@ signals:
|
||||||
void componentPolyCountChanged(QUuid componentId);
|
void componentPolyCountChanged(QUuid componentId);
|
||||||
void componentLayerChanged(QUuid componentId);
|
void componentLayerChanged(QUuid componentId);
|
||||||
void componentClothStiffnessChanged(QUuid componentId);
|
void componentClothStiffnessChanged(QUuid componentId);
|
||||||
|
void componentClothForceChanged(QUuid componentId);
|
||||||
|
void componentClothOffsetChanged(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);
|
||||||
|
@ -653,6 +667,8 @@ public slots:
|
||||||
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 setComponentClothStiffness(QUuid componentId, float stiffness);
|
||||||
|
void setComponentClothForce(QUuid componentId, ClothForce force);
|
||||||
|
void setComponentClothOffset(QUuid componentId, float offset);
|
||||||
void hideOtherComponents(QUuid componentId);
|
void hideOtherComponents(QUuid componentId);
|
||||||
void lockOtherComponents(QUuid componentId);
|
void lockOtherComponents(QUuid componentId);
|
||||||
void hideAllComponents();
|
void hideAllComponents();
|
||||||
|
|
|
@ -1030,6 +1030,8 @@ DocumentWindow::DocumentWindow() :
|
||||||
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::setComponentClothStiffness, m_document, &Document::setComponentClothStiffness);
|
||||||
|
connect(partTreeWidget, &PartTreeWidget::setComponentClothForce, m_document, &Document::setComponentClothForce);
|
||||||
|
connect(partTreeWidget, &PartTreeWidget::setComponentClothOffset, m_document, &Document::setComponentClothOffset);
|
||||||
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);
|
||||||
|
@ -1544,6 +1546,8 @@ void DocumentWindow::showPreferences()
|
||||||
{
|
{
|
||||||
if (nullptr == m_preferencesWidget) {
|
if (nullptr == m_preferencesWidget) {
|
||||||
m_preferencesWidget = new PreferencesWidget(m_document, this);
|
m_preferencesWidget = new PreferencesWidget(m_document, this);
|
||||||
|
connect(m_preferencesWidget, &PreferencesWidget::enableBackgroundBlur, m_document, &Document::enableBackgroundBlur);
|
||||||
|
connect(m_preferencesWidget, &PreferencesWidget::disableBackgroundBlur, m_document, &Document::disableBackgroundBlur);
|
||||||
}
|
}
|
||||||
m_preferencesWidget->show();
|
m_preferencesWidget->show();
|
||||||
m_preferencesWidget->raise();
|
m_preferencesWidget->raise();
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#include "polycount.h"
|
#include "polycount.h"
|
||||||
#include "clothsimulator.h"
|
#include "clothsimulator.h"
|
||||||
#include "isotropicremesh.h"
|
#include "isotropicremesh.h"
|
||||||
|
#include "projectfacestonodes.h"
|
||||||
|
#include "document.h"
|
||||||
|
|
||||||
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
|
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
|
||||||
m_snapshot(snapshot)
|
m_snapshot(snapshot)
|
||||||
|
@ -404,6 +406,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
|
||||||
|
|
||||||
auto &partCache = m_cacheContext->parts[partIdString];
|
auto &partCache = m_cacheContext->parts[partIdString];
|
||||||
partCache.outcomeNodes.clear();
|
partCache.outcomeNodes.clear();
|
||||||
|
partCache.outcomeEdges.clear();
|
||||||
partCache.outcomeNodeVertices.clear();
|
partCache.outcomeNodeVertices.clear();
|
||||||
partCache.outcomePaintMap.clear();
|
partCache.outcomePaintMap.clear();
|
||||||
partCache.outcomePaintMap.partId = partId;
|
partCache.outcomePaintMap.partId = partId;
|
||||||
|
@ -527,6 +530,18 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
|
||||||
partCache.outcomeNodes.push_back(outcomeNode);
|
partCache.outcomeNodes.push_back(outcomeNode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
auto addEdge = [&](const QString &firstNodeIdString, const QString &secondNodeIdString) {
|
||||||
|
partCache.outcomeEdges.push_back({
|
||||||
|
{QUuid(partIdString), QUuid(firstNodeIdString)},
|
||||||
|
{QUuid(partIdString), QUuid(secondNodeIdString)}
|
||||||
|
});
|
||||||
|
if (xMirrored) {
|
||||||
|
partCache.outcomeEdges.push_back({
|
||||||
|
{mirroredPartId, QUuid(firstNodeIdString)},
|
||||||
|
{mirroredPartId, QUuid(secondNodeIdString)}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (gridded) {
|
if (gridded) {
|
||||||
gridMeshBuilder = new GridMeshBuilder;
|
gridMeshBuilder = new GridMeshBuilder;
|
||||||
|
@ -561,6 +576,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
|
||||||
}
|
}
|
||||||
|
|
||||||
gridMeshBuilder->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
gridMeshBuilder->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
||||||
|
addEdge(fromNodeIdString, toNodeIdString);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subdived)
|
if (subdived)
|
||||||
|
@ -618,6 +634,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeMeshModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
nodeMeshModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
|
||||||
|
addEdge(fromNodeIdString, toNodeIdString);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subdived)
|
if (subdived)
|
||||||
|
@ -855,6 +872,11 @@ bool MeshGenerator::componentRemeshed(const std::map<QString, QString> *componen
|
||||||
{
|
{
|
||||||
if (nullptr == component)
|
if (nullptr == component)
|
||||||
return false;
|
return false;
|
||||||
|
if (ComponentLayer::Cloth == ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData())) {
|
||||||
|
if (nullptr != polyCountValue)
|
||||||
|
*polyCountValue = PolyCountToValue(PolyCount::ExtremeHighPoly);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
auto polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(*component, "polyCount").toUtf8().constData());
|
auto polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(*component, "polyCount").toUtf8().constData());
|
||||||
if (nullptr != polyCountValue)
|
if (nullptr != polyCountValue)
|
||||||
*polyCountValue = PolyCountToValue(polyCount);
|
*polyCountValue = PolyCountToValue(polyCount);
|
||||||
|
@ -896,13 +918,27 @@ ComponentLayer MeshGenerator::componentLayer(const std::map<QString, QString> *c
|
||||||
float MeshGenerator::componentClothStiffness(const std::map<QString, QString> *component)
|
float MeshGenerator::componentClothStiffness(const std::map<QString, QString> *component)
|
||||||
{
|
{
|
||||||
if (nullptr == component)
|
if (nullptr == component)
|
||||||
return 1.0f;
|
return Component::defaultStiffness;
|
||||||
auto findClothStiffness = component->find("clothStiffness");
|
auto findClothStiffness = component->find("clothStiffness");
|
||||||
if (findClothStiffness == component->end())
|
if (findClothStiffness == component->end())
|
||||||
return 1.0f;
|
return Component::defaultStiffness;
|
||||||
return findClothStiffness->second.toFloat();
|
return findClothStiffness->second.toFloat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClothForce MeshGenerator::componentClothForce(const std::map<QString, QString> *component)
|
||||||
|
{
|
||||||
|
if (nullptr == component)
|
||||||
|
return ClothForce::Gravitational;
|
||||||
|
return ClothForceFromString(valueOfKeyInMapOrEmpty(*component, "clothForce").toUtf8().constData());
|
||||||
|
}
|
||||||
|
|
||||||
|
float MeshGenerator::componentClothOffset(const std::map<QString, QString> *component)
|
||||||
|
{
|
||||||
|
if (nullptr == component)
|
||||||
|
return 0.0f;
|
||||||
|
return valueOfKeyInMapOrEmpty(*component, "clothOffset").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;
|
||||||
|
@ -933,6 +969,7 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
|
||||||
componentCache.sharedQuadEdges.clear();
|
componentCache.sharedQuadEdges.clear();
|
||||||
componentCache.noneSeamVertices.clear();
|
componentCache.noneSeamVertices.clear();
|
||||||
componentCache.outcomeNodes.clear();
|
componentCache.outcomeNodes.clear();
|
||||||
|
componentCache.outcomeEdges.clear();
|
||||||
componentCache.outcomeNodeVertices.clear();
|
componentCache.outcomeNodeVertices.clear();
|
||||||
componentCache.outcomePaintMaps.clear();
|
componentCache.outcomePaintMaps.clear();
|
||||||
delete componentCache.mesh;
|
delete componentCache.mesh;
|
||||||
|
@ -959,6 +996,8 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
|
||||||
collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges);
|
collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges);
|
||||||
for (const auto &it: partCache.outcomeNodes)
|
for (const auto &it: partCache.outcomeNodes)
|
||||||
componentCache.outcomeNodes.push_back(it);
|
componentCache.outcomeNodes.push_back(it);
|
||||||
|
for (const auto &it: partCache.outcomeEdges)
|
||||||
|
componentCache.outcomeEdges.push_back(it);
|
||||||
for (const auto &it: partCache.outcomeNodeVertices)
|
for (const auto &it: partCache.outcomeNodeVertices)
|
||||||
componentCache.outcomeNodeVertices.push_back(it);
|
componentCache.outcomeNodeVertices.push_back(it);
|
||||||
componentCache.outcomePaintMaps.push_back(partCache.outcomePaintMap);
|
componentCache.outcomePaintMaps.push_back(partCache.outcomePaintMap);
|
||||||
|
@ -1062,7 +1101,12 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
|
||||||
if (componentId.isNull()) {
|
if (componentId.isNull()) {
|
||||||
// Prepare cloth collision shap
|
// Prepare cloth collision shap
|
||||||
if (nullptr != mesh && !mesh->isNull()) {
|
if (nullptr != mesh && !mesh->isNull()) {
|
||||||
|
m_clothCollisionVertices.clear();
|
||||||
|
m_clothCollisionTriangles.clear();
|
||||||
mesh->fetch(m_clothCollisionVertices, m_clothCollisionTriangles);
|
mesh->fetch(m_clothCollisionVertices, m_clothCollisionTriangles);
|
||||||
|
buildClothTargetNodes(componentCache.outcomeNodes,
|
||||||
|
componentCache.outcomeEdges,
|
||||||
|
&m_clothTargetNodes);
|
||||||
} else {
|
} else {
|
||||||
// TODO: when no body is valid, may add ground plane as collision shape
|
// TODO: when no body is valid, may add ground plane as collision shape
|
||||||
// ... ...
|
// ... ...
|
||||||
|
@ -1122,6 +1166,45 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MeshGenerator::buildClothTargetNodes(const std::vector<OutcomeNode> &nodes,
|
||||||
|
const std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> &edges,
|
||||||
|
std::vector<std::pair<QVector3D, float>> *targetNodes)
|
||||||
|
{
|
||||||
|
targetNodes->clear();
|
||||||
|
std::map<std::pair<QUuid, QUuid>, size_t> nodeMap;
|
||||||
|
for (size_t nodeIndex = 0; nodeIndex < nodes.size(); ++nodeIndex) {
|
||||||
|
const auto &it = nodes[nodeIndex];
|
||||||
|
nodeMap.insert({{it.partId, it.nodeId}, nodeIndex});
|
||||||
|
targetNodes->push_back(std::make_pair(it.origin, it.radius * 3.0f));
|
||||||
|
}
|
||||||
|
for (const auto &it: edges) {
|
||||||
|
auto findFirst = nodeMap.find(it.first);
|
||||||
|
if (findFirst == nodeMap.end())
|
||||||
|
continue;
|
||||||
|
auto findSecond = nodeMap.find(it.second);
|
||||||
|
if (findSecond == nodeMap.end())
|
||||||
|
continue;
|
||||||
|
const auto &firstNode = nodes[findFirst->second];
|
||||||
|
const auto &secondNode = nodes[findSecond->second];
|
||||||
|
float length = (firstNode.origin - secondNode.origin).length();
|
||||||
|
float segments = length / 0.02;
|
||||||
|
if (qFuzzyIsNull(segments))
|
||||||
|
continue;
|
||||||
|
if (segments > 100)
|
||||||
|
segments = 100;
|
||||||
|
float segmentLength = 1.0f / segments;
|
||||||
|
float offset = segmentLength;
|
||||||
|
while (offset < 1.0f) {
|
||||||
|
float radius = firstNode.radius * (1.0f - offset) + secondNode.radius * offset;
|
||||||
|
targetNodes->push_back(std::make_pair(
|
||||||
|
firstNode.origin * (1.0f - offset) + secondNode.origin * offset,
|
||||||
|
radius * 3.0
|
||||||
|
));
|
||||||
|
offset += segmentLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MeshCombiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine)
|
MeshCombiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine)
|
||||||
{
|
{
|
||||||
MeshCombiner::Mesh *mesh = nullptr;
|
MeshCombiner::Mesh *mesh = nullptr;
|
||||||
|
@ -1206,6 +1289,8 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vec
|
||||||
componentCache.sharedQuadEdges.insert(it);
|
componentCache.sharedQuadEdges.insert(it);
|
||||||
for (const auto &it: childComponentCache.outcomeNodes)
|
for (const auto &it: childComponentCache.outcomeNodes)
|
||||||
componentCache.outcomeNodes.push_back(it);
|
componentCache.outcomeNodes.push_back(it);
|
||||||
|
for (const auto &it: childComponentCache.outcomeEdges)
|
||||||
|
componentCache.outcomeEdges.push_back(it);
|
||||||
for (const auto &it: childComponentCache.outcomeNodeVertices)
|
for (const auto &it: childComponentCache.outcomeNodeVertices)
|
||||||
componentCache.outcomeNodeVertices.push_back(it);
|
componentCache.outcomeNodeVertices.push_back(it);
|
||||||
for (const auto &it: childComponentCache.outcomePaintMaps)
|
for (const auto &it: childComponentCache.outcomePaintMaps)
|
||||||
|
@ -1600,9 +1685,8 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
|
||||||
if (nullptr == componentCache.mesh) {
|
if (nullptr == componentCache.mesh) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_clothCollisionTriangles.empty())
|
if (m_clothCollisionTriangles.empty() || m_clothTargetNodes.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::vector<QVector3D> uncombinedVertices;
|
std::vector<QVector3D> uncombinedVertices;
|
||||||
std::vector<std::vector<size_t>> uncombinedOriginalFaces;
|
std::vector<std::vector<size_t>> uncombinedOriginalFaces;
|
||||||
std::vector<std::vector<size_t>> uncombinedFaces;
|
std::vector<std::vector<size_t>> uncombinedFaces;
|
||||||
|
@ -1620,7 +1704,6 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
|
||||||
uncombinedFaces.push_back(it);
|
uncombinedFaces.push_back(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
std::pair<QUuid, QUuid> defaultSource;
|
||||||
|
@ -1637,15 +1720,37 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
|
||||||
uncombinedVertexSources[i] = findSource->second;
|
uncombinedVertexSources[i] = findSource->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClothSimulator clothSimulator(uncombinedVertices,
|
std::vector<QVector3D> &filteredClothVertices = uncombinedVertices;
|
||||||
uncombinedFaces,
|
std::vector<std::vector<size_t>> &filteredClothFaces = uncombinedFaces;
|
||||||
|
std::vector<QVector3D> externalForces;
|
||||||
|
ClothForce clothForce = componentClothForce(component);
|
||||||
|
float clothOffset = 0.015f + (componentClothOffset(component) * 0.05f);
|
||||||
|
if (ClothForce::Centripetal == clothForce) {
|
||||||
|
externalForces.resize(filteredClothVertices.size());
|
||||||
|
for (size_t i = 0; i < filteredClothFaces.size(); ++i) {
|
||||||
|
const auto &face = filteredClothFaces[i];
|
||||||
|
auto faceForceDirection = -polygonNormal(uncombinedVertices, face);
|
||||||
|
for (const auto &vertex: face)
|
||||||
|
externalForces[vertex] += faceForceDirection;
|
||||||
|
}
|
||||||
|
for (auto &it: externalForces)
|
||||||
|
it = (it.normalized() + QVector3D(0.0f, -1.0f, 0.0f)).normalized();
|
||||||
|
} else {
|
||||||
|
externalForces.resize(filteredClothVertices.size(), QVector3D(0.0f, -1.0f, 0.0f));
|
||||||
|
}
|
||||||
|
ClothSimulator clothSimulator(filteredClothVertices,
|
||||||
|
filteredClothFaces,
|
||||||
m_clothCollisionVertices,
|
m_clothCollisionVertices,
|
||||||
m_clothCollisionTriangles);
|
m_clothCollisionTriangles,
|
||||||
|
externalForces);
|
||||||
clothSimulator.setStiffness(componentClothStiffness(component));
|
clothSimulator.setStiffness(componentClothStiffness(component));
|
||||||
clothSimulator.create();
|
clothSimulator.create();
|
||||||
for (size_t i = 0; i < 400; ++i)
|
for (size_t i = 0; i < 350; ++i)
|
||||||
clothSimulator.step();
|
clothSimulator.step();
|
||||||
clothSimulator.getCurrentVertices(&uncombinedVertices);
|
clothSimulator.getCurrentVertices(&filteredClothVertices);
|
||||||
|
for (size_t i = 0; i < filteredClothVertices.size(); ++i) {
|
||||||
|
filteredClothVertices[i] -= externalForces[i] * clothOffset;
|
||||||
|
}
|
||||||
|
|
||||||
auto vertexStartIndex = m_outcome->vertices.size();
|
auto vertexStartIndex = m_outcome->vertices.size();
|
||||||
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
|
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
|
||||||
|
@ -1654,10 +1759,10 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
|
||||||
subIt += vertexStartIndex;
|
subIt += vertexStartIndex;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
updateVertexIndices(uncombinedFaces);
|
updateVertexIndices(filteredClothFaces);
|
||||||
|
|
||||||
m_outcome->vertices.insert(m_outcome->vertices.end(), uncombinedVertices.begin(), uncombinedVertices.end());
|
m_outcome->vertices.insert(m_outcome->vertices.end(), filteredClothVertices.begin(), filteredClothVertices.end());
|
||||||
for (const auto &it: uncombinedFaces) {
|
for (const auto &it: filteredClothFaces) {
|
||||||
if (4 == it.size()) {
|
if (4 == it.size()) {
|
||||||
m_outcome->triangles.push_back(std::vector<size_t> {
|
m_outcome->triangles.push_back(std::vector<size_t> {
|
||||||
it[0], it[1], it[2]
|
it[0], it[1], it[2]
|
||||||
|
@ -1669,14 +1774,13 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
|
||||||
m_outcome->triangles.push_back(it);
|
m_outcome->triangles.push_back(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), uncombinedFaces.begin(), uncombinedFaces.end());
|
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), filteredClothFaces.begin(), filteredClothFaces.end());
|
||||||
|
|
||||||
m_outcome->nodes.insert(m_outcome->nodes.end(), componentCache.outcomeNodes.begin(), componentCache.outcomeNodes.end());
|
m_outcome->nodes.insert(m_outcome->nodes.end(), componentCache.outcomeNodes.begin(), componentCache.outcomeNodes.end());
|
||||||
|
Q_ASSERT(uncombinedVertices.size() == filteredClothVertices.size());
|
||||||
for (size_t i = 0; i < uncombinedVertices.size(); ++i) {
|
for (size_t i = 0; i < uncombinedVertices.size(); ++i) {
|
||||||
const auto &source = uncombinedVertexSources[i];
|
const auto &source = uncombinedVertexSources[i];
|
||||||
if (source.first.isNull())
|
m_outcome->nodeVertices.push_back(std::make_pair(filteredClothVertices[i], source));
|
||||||
continue;
|
|
||||||
m_outcome->nodeVertices.push_back(std::make_pair(uncombinedVertices[i], source));
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "combinemode.h"
|
#include "combinemode.h"
|
||||||
#include "meshloader.h"
|
#include "meshloader.h"
|
||||||
#include "componentlayer.h"
|
#include "componentlayer.h"
|
||||||
|
#include "clothforce.h"
|
||||||
|
|
||||||
class GeneratedPart
|
class GeneratedPart
|
||||||
{
|
{
|
||||||
|
@ -24,6 +25,7 @@ public:
|
||||||
std::vector<QVector3D> vertices;
|
std::vector<QVector3D> vertices;
|
||||||
std::vector<std::vector<size_t>> faces;
|
std::vector<std::vector<size_t>> faces;
|
||||||
std::vector<OutcomeNode> outcomeNodes;
|
std::vector<OutcomeNode> outcomeNodes;
|
||||||
|
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> outcomeEdges;
|
||||||
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
|
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
|
||||||
std::vector<QVector3D> previewVertices;
|
std::vector<QVector3D> previewVertices;
|
||||||
std::vector<std::vector<size_t>> previewTriangles;
|
std::vector<std::vector<size_t>> previewTriangles;
|
||||||
|
@ -43,6 +45,7 @@ public:
|
||||||
std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges;
|
std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges;
|
||||||
std::set<PositionKey> noneSeamVertices;
|
std::set<PositionKey> noneSeamVertices;
|
||||||
std::vector<OutcomeNode> outcomeNodes;
|
std::vector<OutcomeNode> outcomeNodes;
|
||||||
|
std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> outcomeEdges;
|
||||||
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
|
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> outcomeNodeVertices;
|
||||||
std::vector<OutcomePaintMap> outcomePaintMaps;
|
std::vector<OutcomePaintMap> outcomePaintMaps;
|
||||||
};
|
};
|
||||||
|
@ -103,6 +106,7 @@ private:
|
||||||
quint64 m_id = 0;
|
quint64 m_id = 0;
|
||||||
std::vector<QVector3D> m_clothCollisionVertices;
|
std::vector<QVector3D> m_clothCollisionVertices;
|
||||||
std::vector<std::vector<size_t>> m_clothCollisionTriangles;
|
std::vector<std::vector<size_t>> m_clothCollisionTriangles;
|
||||||
|
std::vector<std::pair<QVector3D, float>> m_clothTargetNodes;
|
||||||
|
|
||||||
void collectParts();
|
void collectParts();
|
||||||
bool checkIsComponentDirty(const QString &componentIdString);
|
bool checkIsComponentDirty(const QString &componentIdString);
|
||||||
|
@ -130,6 +134,8 @@ private:
|
||||||
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);
|
float componentClothStiffness(const std::map<QString, QString> *component);
|
||||||
|
ClothForce componentClothForce(const std::map<QString, QString> *component);
|
||||||
|
float componentClothOffset(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);
|
||||||
|
@ -141,6 +147,9 @@ private:
|
||||||
std::vector<std::vector<size_t>> *outputQuads,
|
std::vector<std::vector<size_t>> *outputQuads,
|
||||||
std::vector<std::vector<size_t>> *outputTriangles,
|
std::vector<std::vector<size_t>> *outputTriangles,
|
||||||
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> *outputNodeVertices);
|
std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> *outputNodeVertices);
|
||||||
|
void buildClothTargetNodes(const std::vector<OutcomeNode> &nodes,
|
||||||
|
const std::vector<std::pair<std::pair<QUuid, QUuid>, std::pair<QUuid, QUuid>>> &edges,
|
||||||
|
std::vector<std::pair<QVector3D, float>> *targetNodes);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -246,8 +246,43 @@ void PartTreeWidget::showClothSettingMenu(const QPoint &pos, const QUuid &compon
|
||||||
clothStiffnessLayout->addWidget(clothStiffnessEraser);
|
clothStiffnessLayout->addWidget(clothStiffnessEraser);
|
||||||
clothStiffnessLayout->addWidget(clothStiffnessWidget);
|
clothStiffnessLayout->addWidget(clothStiffnessWidget);
|
||||||
|
|
||||||
|
FloatNumberWidget *clothOffsetWidget = new FloatNumberWidget;
|
||||||
|
clothOffsetWidget->setItemName(tr("Offset"));
|
||||||
|
clothOffsetWidget->setRange(0.0f, 1.0f);
|
||||||
|
clothOffsetWidget->setValue(component->clothOffset);
|
||||||
|
|
||||||
|
connect(clothOffsetWidget, &FloatNumberWidget::valueChanged, [=](float value) {
|
||||||
|
emit setComponentClothOffset(componentId, value);
|
||||||
|
emit groupOperationAdded();
|
||||||
|
});
|
||||||
|
|
||||||
|
QPushButton *clothOffsetEraser = new QPushButton(QChar(fa::eraser));
|
||||||
|
Theme::initAwesomeToolButton(clothOffsetEraser);
|
||||||
|
|
||||||
|
QHBoxLayout *clothOffsetLayout = new QHBoxLayout;
|
||||||
|
clothOffsetLayout->addWidget(clothOffsetEraser);
|
||||||
|
clothOffsetLayout->addWidget(clothOffsetWidget);
|
||||||
|
|
||||||
|
QComboBox *clothForceSelectBox = new QComboBox;
|
||||||
|
for (size_t i = 0; i < (size_t)ClothForce::Count; ++i) {
|
||||||
|
ClothForce force = (ClothForce)i;
|
||||||
|
clothForceSelectBox->addItem(ClothForceToDispName(force));
|
||||||
|
}
|
||||||
|
clothForceSelectBox->setCurrentIndex((int)component->clothForce);
|
||||||
|
connect(clothForceSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
|
||||||
|
emit setComponentClothForce(component->id, (ClothForce)index);
|
||||||
|
emit groupOperationAdded();
|
||||||
|
});
|
||||||
|
QFormLayout *clothForceFormLayout = new QFormLayout;
|
||||||
|
clothForceFormLayout->addRow(tr("Force"), clothForceSelectBox);
|
||||||
|
QHBoxLayout *clothForceLayout = new QHBoxLayout;
|
||||||
|
clothForceLayout->addLayout(clothForceFormLayout);
|
||||||
|
clothForceLayout->addStretch();
|
||||||
|
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
mainLayout->addLayout(clothStiffnessLayout);
|
mainLayout->addLayout(clothStiffnessLayout);
|
||||||
|
mainLayout->addLayout(clothOffsetLayout);
|
||||||
|
mainLayout->addLayout(clothForceLayout);
|
||||||
|
|
||||||
popup->setLayout(mainLayout);
|
popup->setLayout(mainLayout);
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,8 @@ signals:
|
||||||
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 setComponentClothStiffness(QUuid componentId, float clothStiffness);
|
||||||
|
void setComponentClothForce(QUuid componentId, ClothForce force);
|
||||||
|
void setComponentClothOffset(QUuid componentId, float offset);
|
||||||
void hideDescendantComponents(QUuid componentId);
|
void hideDescendantComponents(QUuid componentId);
|
||||||
void showDescendantComponents(QUuid componentId);
|
void showDescendantComponents(QUuid componentId);
|
||||||
void lockDescendantComponents(QUuid componentId);
|
void lockDescendantComponents(QUuid componentId);
|
||||||
|
|
|
@ -40,12 +40,14 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(pickButton, &QPushButton::clicked, [=]() {
|
connect(pickButton, &QPushButton::clicked, [=]() {
|
||||||
|
emit disableBackgroundBlur();
|
||||||
QColor color = QColorDialog::getColor(Preferences::instance().partColor(), this);
|
QColor color = QColorDialog::getColor(Preferences::instance().partColor(), this);
|
||||||
if (color.isValid()) {
|
if (color.isValid()) {
|
||||||
Preferences::instance().setPartColor(color);
|
Preferences::instance().setPartColor(color);
|
||||||
updatePickButtonColor();
|
updatePickButtonColor();
|
||||||
raise();
|
raise();
|
||||||
}
|
}
|
||||||
|
emit enableBackgroundBlur();
|
||||||
});
|
});
|
||||||
|
|
||||||
QComboBox *combineModeSelectBox = new QComboBox;
|
QComboBox *combineModeSelectBox = new QComboBox;
|
||||||
|
|
|
@ -8,6 +8,8 @@ class PreferencesWidget : public QDialog
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
signals:
|
signals:
|
||||||
void enableWeld(bool enabled);
|
void enableWeld(bool enabled);
|
||||||
|
void enableBackgroundBlur();
|
||||||
|
void disableBackgroundBlur();
|
||||||
public:
|
public:
|
||||||
PreferencesWidget(const Document *document, QWidget *parent=nullptr);
|
PreferencesWidget(const Document *document, QWidget *parent=nullptr);
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
#include <tbb/blocked_range.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include "projectfacestonodes.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
class FacesToNodesProjector
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FacesToNodesProjector(const std::vector<QVector3D> *vertices,
|
||||||
|
const std::vector<std::vector<size_t>> *faces,
|
||||||
|
const std::vector<std::pair<QVector3D, float>> *sourceNodes,
|
||||||
|
std::vector<size_t> *faceSources) :
|
||||||
|
m_vertices(vertices),
|
||||||
|
m_faces(faces),
|
||||||
|
m_sourceNodes(sourceNodes),
|
||||||
|
m_faceSources(faceSources)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
bool intersectionTest(const std::vector<size_t> &face,
|
||||||
|
const QVector3D &nodePosition, float nodeRadius,
|
||||||
|
float *distance2) const
|
||||||
|
{
|
||||||
|
QVector3D faceCenter;
|
||||||
|
for (const auto &it: face) {
|
||||||
|
faceCenter += (*m_vertices)[it];
|
||||||
|
}
|
||||||
|
if (face.size() > 0)
|
||||||
|
faceCenter /= face.size();
|
||||||
|
const auto &A = faceCenter;
|
||||||
|
const auto &C = nodePosition;
|
||||||
|
auto B = -polygonNormal(*m_vertices, face);
|
||||||
|
auto a = QVector3D::dotProduct(B, B);
|
||||||
|
auto b = 2.0f * QVector3D::dotProduct(B, A - C);
|
||||||
|
const auto &r = nodeRadius;
|
||||||
|
auto c = QVector3D::dotProduct(A - C, A - C) - r * r;
|
||||||
|
if (b * b - 4 * a * c <= 0)
|
||||||
|
return false;
|
||||||
|
*distance2 = (faceCenter - nodePosition).lengthSquared();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
void operator()(const tbb::blocked_range<size_t> &range) const
|
||||||
|
{
|
||||||
|
for (size_t i = range.begin(); i != range.end(); ++i) {
|
||||||
|
std::vector<std::pair<size_t, float>> distance2WithNodes;
|
||||||
|
for (size_t j = 0; j < m_sourceNodes->size(); ++j) {
|
||||||
|
const auto &node = (*m_sourceNodes)[j];
|
||||||
|
float distance2 = 0.0f;
|
||||||
|
if (!intersectionTest((*m_faces)[i], node.first, node.second, &distance2))
|
||||||
|
continue;
|
||||||
|
distance2WithNodes.push_back(std::make_pair(j, distance2));
|
||||||
|
}
|
||||||
|
if (distance2WithNodes.empty())
|
||||||
|
continue;
|
||||||
|
(*m_faceSources)[i] = std::min_element(distance2WithNodes.begin(), distance2WithNodes.end(), [](const std::pair<size_t, float> &first, const std::pair<size_t, float> &second) {
|
||||||
|
return first.second < second.second;
|
||||||
|
})->first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const std::vector<QVector3D> *m_vertices = nullptr;
|
||||||
|
const std::vector<std::vector<size_t>> *m_faces = nullptr;
|
||||||
|
const std::vector<std::pair<QVector3D, float>> *m_sourceNodes = nullptr;
|
||||||
|
std::vector<size_t> *m_faceSources = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
void projectFacesToNodes(const std::vector<QVector3D> &vertices,
|
||||||
|
const std::vector<std::vector<size_t>> &faces,
|
||||||
|
const std::vector<std::pair<QVector3D, float>> &sourceNodes,
|
||||||
|
std::vector<size_t> *faceSources)
|
||||||
|
{
|
||||||
|
faceSources->resize(faces.size());
|
||||||
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, faces.size()),
|
||||||
|
FacesToNodesProjector(&vertices, &faces, &sourceNodes, faceSources));
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
#ifndef DUST3D_PROJECT_TRIANGLES_TO_NODES_H
|
||||||
|
#define DUST3D_PROJECT_TRIANGLES_TO_NODES_H
|
||||||
|
#include <QVector3D>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
void projectFacesToNodes(const std::vector<QVector3D> &vertices,
|
||||||
|
const std::vector<std::vector<size_t>> &faces,
|
||||||
|
const std::vector<std::pair<QVector3D, float>> &sourceNodes,
|
||||||
|
std::vector<size_t> *faceSources);
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,6 +1,9 @@
|
||||||
#include <instant-meshes-api.h>
|
#include <instant-meshes-api.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include "remesher.h"
|
#include "remesher.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "projectfacestonodes.h"
|
||||||
|
|
||||||
Remesher::Remesher()
|
Remesher::Remesher()
|
||||||
{
|
{
|
||||||
|
@ -43,6 +46,7 @@ void Remesher::remesh(float targetVertexMultiplyFactor)
|
||||||
}
|
}
|
||||||
std::vector<Dust3D_InstantMeshesTriangle> inputTriangles;
|
std::vector<Dust3D_InstantMeshesTriangle> inputTriangles;
|
||||||
inputTriangles.reserve(m_triangles.size());
|
inputTriangles.reserve(m_triangles.size());
|
||||||
|
float totalArea = 0.0f;
|
||||||
for (size_t i = 0; i < m_triangles.size(); ++i) {
|
for (size_t i = 0; i < m_triangles.size(); ++i) {
|
||||||
const auto &triangle = m_triangles[i];
|
const auto &triangle = m_triangles[i];
|
||||||
if (triangle.size() != 3)
|
if (triangle.size() != 3)
|
||||||
|
@ -52,6 +56,7 @@ void Remesher::remesh(float targetVertexMultiplyFactor)
|
||||||
triangle[1],
|
triangle[1],
|
||||||
triangle[2]
|
triangle[2]
|
||||||
}});
|
}});
|
||||||
|
totalArea += areaOfTriangle(m_vertices[triangle[0]], m_vertices[triangle[1]], m_vertices[triangle[2]]);
|
||||||
}
|
}
|
||||||
const Dust3D_InstantMeshesVertex *resultVertices = nullptr;
|
const Dust3D_InstantMeshesVertex *resultVertices = nullptr;
|
||||||
size_t nResultVertices = 0;
|
size_t nResultVertices = 0;
|
||||||
|
@ -61,7 +66,7 @@ void Remesher::remesh(float targetVertexMultiplyFactor)
|
||||||
size_t nResultQuads = 0;
|
size_t nResultQuads = 0;
|
||||||
Dust3D_instantMeshesRemesh(inputVertices.data(), inputVertices.size(),
|
Dust3D_instantMeshesRemesh(inputVertices.data(), inputVertices.size(),
|
||||||
inputTriangles.data(), inputTriangles.size(),
|
inputTriangles.data(), inputTriangles.size(),
|
||||||
(size_t)(inputVertices.size() * targetVertexMultiplyFactor),
|
(size_t)(targetVertexMultiplyFactor * 30 * std::sqrt(totalArea) / 0.02f),
|
||||||
&resultVertices,
|
&resultVertices,
|
||||||
&nResultVertices,
|
&nResultVertices,
|
||||||
&resultTriangles,
|
&resultTriangles,
|
||||||
|
@ -100,27 +105,25 @@ void Remesher::setNodes(const std::vector<std::pair<QVector3D, float>> &nodes,
|
||||||
|
|
||||||
void Remesher::resolveSources()
|
void Remesher::resolveSources()
|
||||||
{
|
{
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
std::vector<size_t> faceSources;
|
||||||
|
auto projectFacesToNodesStartTime = timer.elapsed();
|
||||||
|
projectFacesToNodes(m_remeshedVertices, m_remeshedFaces, m_nodes, &faceSources);
|
||||||
|
auto projectFacesToNodesStopTime = timer.elapsed();
|
||||||
|
qDebug() << "Project faces to nodes took" << (projectFacesToNodesStopTime - projectFacesToNodesStartTime) << "milliseconds";
|
||||||
|
std::map<size_t, std::map<size_t, size_t>> vertexToNodeVotes;
|
||||||
|
for (size_t i = 0; i < m_remeshedFaces.size(); ++i) {
|
||||||
|
const auto &face = m_remeshedFaces[i];
|
||||||
|
const auto &source = faceSources[i];
|
||||||
|
for (const auto &vertexIndex: face)
|
||||||
|
vertexToNodeVotes[vertexIndex][source]++;
|
||||||
|
}
|
||||||
m_remeshedVertexSources.resize(m_remeshedVertices.size());
|
m_remeshedVertexSources.resize(m_remeshedVertices.size());
|
||||||
std::vector<float> nodeRadius2(m_nodes.size());
|
for (const auto &it: vertexToNodeVotes) {
|
||||||
for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) {
|
auto sourceIndex = std::max_element(it.second.begin(), it.second.end(), [](const std::pair<size_t, size_t> &first, const std::pair<size_t, size_t> &second) {
|
||||||
nodeRadius2[nodeIndex] = std::pow(m_nodes[nodeIndex].second, 2);
|
return first.second < second.second;
|
||||||
}
|
})->first;
|
||||||
for (size_t vertexIndex = 0; vertexIndex < m_remeshedVertices.size(); ++vertexIndex) {
|
m_remeshedVertexSources[it.first] = m_sourceIds[sourceIndex];
|
||||||
std::vector<std::pair<float, size_t>> matches;
|
|
||||||
const auto &vertexPosition = m_remeshedVertices[vertexIndex];
|
|
||||||
for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) {
|
|
||||||
const auto &nodePosition = m_nodes[nodeIndex].first;
|
|
||||||
auto length2 = (vertexPosition - nodePosition).lengthSquared();
|
|
||||||
if (length2 > nodeRadius2[nodeIndex])
|
|
||||||
continue;
|
|
||||||
matches.push_back(std::make_pair(length2, nodeIndex));
|
|
||||||
}
|
|
||||||
std::sort(matches.begin(), matches.end(), [](const std::pair<float, size_t> &first,
|
|
||||||
const std::pair<float, size_t> &second) {
|
|
||||||
return first.first < second.first;
|
|
||||||
});
|
|
||||||
if (matches.empty())
|
|
||||||
continue;
|
|
||||||
m_remeshedVertexSources[vertexIndex] = m_sourceIds[matches[0].second];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue