diff --git a/dust3d.pro b/dust3d.pro
index 4d41320d..83a25525 100644
--- a/dust3d.pro
+++ b/dust3d.pro
@@ -491,6 +491,12 @@ HEADERS += src/componentlayer.h
SOURCES += src/isotropicremesh.cpp
HEADERS += src/isotropicremesh.h
+SOURCES += src/clothforce.cpp
+HEADERS += src/clothforce.h
+
+SOURCES += src/projectfacestonodes.cpp
+HEADERS += src/projectfacestonodes.h
+
SOURCES += src/main.cpp
HEADERS += src/version.h
diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts
index 6d65a7e2..d02f909e 100644
--- a/languages/dust3d_zh_CN.ts
+++ b/languages/dust3d_zh_CN.ts
@@ -756,6 +756,14 @@ Tips:
硬度
+
+
+ 力
+
+
+
+ 偏移
+
PartWidget
@@ -1086,6 +1094,14 @@ Tips:
衣服
+
+
+ 重力
+
+
+
+ 向心力
+
RigWidget
diff --git a/src/clothforce.cpp b/src/clothforce.cpp
new file mode 100644
index 00000000..9bff2b12
--- /dev/null
+++ b/src/clothforce.cpp
@@ -0,0 +1,6 @@
+#include
+#include "clothforce.h"
+
+IMPL_ClothForceFromString
+IMPL_ClothForceToString
+IMPL_ClothForceToDispName
diff --git a/src/clothforce.h b/src/clothforce.h
new file mode 100644
index 00000000..2dd429e0
--- /dev/null
+++ b/src/clothforce.h
@@ -0,0 +1,49 @@
+#ifndef DUST3D_CLOTH_FORCE_H
+#define DUST3D_CLOTH_FORCE_H
+#include
+
+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
diff --git a/src/clothsimulator.cpp b/src/clothsimulator.cpp
index 23d686eb..9e2d3daf 100644
--- a/src/clothsimulator.cpp
+++ b/src/clothsimulator.cpp
@@ -112,7 +112,8 @@ public:
}
}
- void fixPoints(CgPointFixNode *fixNode) {
+ void fixPoints(CgPointFixNode *fixNode)
+ {
for (unsigned int i = 0; i < system->n_points; i++) {
auto offset = 3 * i;
Point point(vbuff[offset + 0],
@@ -124,16 +125,32 @@ public:
}
}
}
+
+ void collectErrorPoints(std::vector *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 &vertices,
const std::vector> &faces,
const std::vector &collisionVertices,
- const std::vector> &collisionTriangles) :
+ const std::vector> &collisionTriangles,
+ const std::vector &externalForces) :
m_vertices(vertices),
m_faces(faces),
m_collisionVertices(collisionVertices),
- m_collisionTriangles(collisionTriangles)
+ m_collisionTriangles(collisionTriangles),
+ m_externalForces(externalForces)
{
}
@@ -192,7 +209,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] + 0.01,
+ m_clothPointBuffer[offset + 1],
m_clothPointBuffer[offset + 2]);
}
}
@@ -230,11 +247,18 @@ void ClothSimulator::create()
for (size_t i = 0; i < m_clothSprings.size(); ++i) {
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();
+ restLengths[i] = (m_vertices[m_clothPointSources[source.first]] - m_vertices[m_clothPointSources[source.second]]).length() * 0.8;
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,
stiffnesses, masses, fext, damping);
diff --git a/src/clothsimulator.h b/src/clothsimulator.h
index b8b7e7e0..9b95f0f5 100644
--- a/src/clothsimulator.h
+++ b/src/clothsimulator.h
@@ -18,7 +18,8 @@ public:
ClothSimulator(const std::vector &vertices,
const std::vector> &faces,
const std::vector &collisionVertices,
- const std::vector> &collisionTriangles);
+ const std::vector> &collisionTriangles,
+ const std::vector &externalForces);
~ClothSimulator();
void setStiffness(float stiffness);
void create();
@@ -29,10 +30,12 @@ private:
std::vector> m_faces;
std::vector m_collisionVertices;
std::vector> m_collisionTriangles;
+ std::vector m_externalForces;
std::vector m_clothPointBuffer;
std::vector m_clothPointSources;
std::vector> m_clothSprings;
float m_stiffness = 1.0f;
+ QVector3D m_offset;
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 8cfe5bc5..27466535 100644
--- a/src/document.cpp
+++ b/src/document.cpp
@@ -21,6 +21,7 @@
#include "contourtopartconverter.h"
unsigned long Document::m_maxSnapshot = 1000;
+const float Component::defaultStiffness = 0.5f;
Document::Document() :
SkeletonDocument(),
@@ -1205,6 +1206,10 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId
component["layer"] = ComponentLayerToString(componentIt.second.layer);
if (componentIt.second.clothStiffnessAdjusted())
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;
for (const auto &childId: componentIt.second.childrenIds) {
childIdList.append(childId.toString());
@@ -1716,6 +1721,12 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
auto findClothStiffness = componentKv.second.find("clothStiffness");
if (findClothStiffness != componentKv.second.end())
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;
if ("partId" == linkDataType) {
QUuid partId = oldNewIdMap[QUuid(linkData)];
@@ -2533,6 +2544,34 @@ void Document::setComponentClothStiffness(QUuid componentId, float stiffness)
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)
{
auto component = componentMap.find(componentId);
diff --git a/src/document.h b/src/document.h
index 21ea0c5a..7cd655b6 100644
--- a/src/document.h
+++ b/src/document.h
@@ -31,6 +31,7 @@
#include "paintmode.h"
#include "proceduralanimation.h"
#include "componentlayer.h"
+#include "clothforce.h"
class MaterialPreviewsGenerator;
class MotionsGenerator;
@@ -46,6 +47,7 @@ public:
class Component
{
public:
+ static const float defaultStiffness;
Component()
{
}
@@ -69,7 +71,9 @@ public:
float smoothSeam = 0.0;
PolyCount polyCount = PolyCount::Original;
ComponentLayer layer = ComponentLayer::Body;
- float clothStiffness = 1.0f;
+ float clothStiffness = defaultStiffness;
+ ClothForce clothForce = ClothForce::Gravitational;
+ float clothOffset = 0.0f;
std::vector childrenIds;
QString linkData() const
{
@@ -191,7 +195,15 @@ public:
}
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:
std::set m_childrenIdSet;
@@ -405,6 +417,8 @@ signals:
void componentPolyCountChanged(QUuid componentId);
void componentLayerChanged(QUuid componentId);
void componentClothStiffnessChanged(QUuid componentId);
+ void componentClothForceChanged(QUuid componentId);
+ void componentClothOffsetChanged(QUuid componentId);
void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId);
@@ -653,6 +667,8 @@ public slots:
void setComponentPolyCount(QUuid componentId, PolyCount count);
void setComponentLayer(QUuid componentId, ComponentLayer layer);
void setComponentClothStiffness(QUuid componentId, float stiffness);
+ void setComponentClothForce(QUuid componentId, ClothForce force);
+ void setComponentClothOffset(QUuid componentId, float offset);
void hideOtherComponents(QUuid componentId);
void lockOtherComponents(QUuid componentId);
void hideAllComponents();
diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp
index b07d75cb..83a7058a 100644
--- a/src/documentwindow.cpp
+++ b/src/documentwindow.cpp
@@ -1030,6 +1030,8 @@ DocumentWindow::DocumentWindow() :
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::setComponentClothForce, m_document, &Document::setComponentClothForce);
+ connect(partTreeWidget, &PartTreeWidget::setComponentClothOffset, m_document, &Document::setComponentClothOffset);
connect(partTreeWidget, &PartTreeWidget::setPartTarget, m_document, &Document::setPartTarget);
connect(partTreeWidget, &PartTreeWidget::setPartBase, m_document, &Document::setPartBase);
connect(partTreeWidget, &PartTreeWidget::hideDescendantComponents, m_document, &Document::hideDescendantComponents);
@@ -1544,6 +1546,8 @@ void DocumentWindow::showPreferences()
{
if (nullptr == m_preferencesWidget) {
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->raise();
diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp
index 97d381f3..2ba52067 100644
--- a/src/meshgenerator.cpp
+++ b/src/meshgenerator.cpp
@@ -20,6 +20,8 @@
#include "polycount.h"
#include "clothsimulator.h"
#include "isotropicremesh.h"
+#include "projectfacestonodes.h"
+#include "document.h"
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
m_snapshot(snapshot)
@@ -404,6 +406,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
auto &partCache = m_cacheContext->parts[partIdString];
partCache.outcomeNodes.clear();
+ partCache.outcomeEdges.clear();
partCache.outcomeNodeVertices.clear();
partCache.outcomePaintMap.clear();
partCache.outcomePaintMap.partId = partId;
@@ -527,6 +530,18 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
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) {
gridMeshBuilder = new GridMeshBuilder;
@@ -561,6 +576,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
}
gridMeshBuilder->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
+ addEdge(fromNodeIdString, toNodeIdString);
}
if (subdived)
@@ -618,6 +634,7 @@ MeshCombiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString,
}
nodeMeshModifier->addEdge(findFromNodeIndex->second, findToNodeIndex->second);
+ addEdge(fromNodeIdString, toNodeIdString);
}
if (subdived)
@@ -855,6 +872,11 @@ bool MeshGenerator::componentRemeshed(const std::map *componen
{
if (nullptr == component)
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());
if (nullptr != polyCountValue)
*polyCountValue = PolyCountToValue(polyCount);
@@ -896,13 +918,27 @@ ComponentLayer MeshGenerator::componentLayer(const std::map *c
float MeshGenerator::componentClothStiffness(const std::map *component)
{
if (nullptr == component)
- return 1.0f;
+ return Component::defaultStiffness;
auto findClothStiffness = component->find("clothStiffness");
if (findClothStiffness == component->end())
- return 1.0f;
+ return Component::defaultStiffness;
return findClothStiffness->second.toFloat();
}
+ClothForce MeshGenerator::componentClothForce(const std::map *component)
+{
+ if (nullptr == component)
+ return ClothForce::Gravitational;
+ return ClothForceFromString(valueOfKeyInMapOrEmpty(*component, "clothForce").toUtf8().constData());
+}
+
+float MeshGenerator::componentClothOffset(const std::map *component)
+{
+ if (nullptr == component)
+ return 0.0f;
+ return valueOfKeyInMapOrEmpty(*component, "clothOffset").toFloat();
+}
+
MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode)
{
MeshCombiner::Mesh *mesh = nullptr;
@@ -933,6 +969,7 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
componentCache.sharedQuadEdges.clear();
componentCache.noneSeamVertices.clear();
componentCache.outcomeNodes.clear();
+ componentCache.outcomeEdges.clear();
componentCache.outcomeNodeVertices.clear();
componentCache.outcomePaintMaps.clear();
delete componentCache.mesh;
@@ -959,6 +996,8 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
collectSharedQuadEdges(partCache.vertices, partCache.faces, &componentCache.sharedQuadEdges);
for (const auto &it: partCache.outcomeNodes)
componentCache.outcomeNodes.push_back(it);
+ for (const auto &it: partCache.outcomeEdges)
+ componentCache.outcomeEdges.push_back(it);
for (const auto &it: partCache.outcomeNodeVertices)
componentCache.outcomeNodeVertices.push_back(it);
componentCache.outcomePaintMaps.push_back(partCache.outcomePaintMap);
@@ -1062,7 +1101,12 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
if (componentId.isNull()) {
// Prepare cloth collision shap
if (nullptr != mesh && !mesh->isNull()) {
+ m_clothCollisionVertices.clear();
+ m_clothCollisionTriangles.clear();
mesh->fetch(m_clothCollisionVertices, m_clothCollisionTriangles);
+ buildClothTargetNodes(componentCache.outcomeNodes,
+ componentCache.outcomeEdges,
+ &m_clothTargetNodes);
} else {
// 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;
}
+void MeshGenerator::buildClothTargetNodes(const std::vector &nodes,
+ const std::vector, std::pair>> &edges,
+ std::vector> *targetNodes)
+{
+ targetNodes->clear();
+ std::map, 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> &multipleMeshes, bool recombine)
{
MeshCombiner::Mesh *mesh = nullptr;
@@ -1206,6 +1289,8 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentChildGroupMesh(const std::vec
componentCache.sharedQuadEdges.insert(it);
for (const auto &it: childComponentCache.outcomeNodes)
componentCache.outcomeNodes.push_back(it);
+ for (const auto &it: childComponentCache.outcomeEdges)
+ componentCache.outcomeEdges.push_back(it);
for (const auto &it: childComponentCache.outcomeNodeVertices)
componentCache.outcomeNodeVertices.push_back(it);
for (const auto &it: childComponentCache.outcomePaintMaps)
@@ -1600,9 +1685,8 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
if (nullptr == componentCache.mesh) {
return;
}
- if (m_clothCollisionTriangles.empty())
+ if (m_clothCollisionTriangles.empty() || m_clothTargetNodes.empty())
return;
-
std::vector uncombinedVertices;
std::vector> uncombinedOriginalFaces;
std::vector> uncombinedFaces;
@@ -1620,7 +1704,6 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
uncombinedFaces.push_back(it);
}
}
- isotropicRemesh(uncombinedVertices, uncombinedFaces, uncombinedVertices, uncombinedFaces, 0.02f, 3);
std::map> positionMap;
std::pair defaultSource;
@@ -1637,15 +1720,37 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
uncombinedVertexSources[i] = findSource->second;
}
- ClothSimulator clothSimulator(uncombinedVertices,
- uncombinedFaces,
+ std::vector &filteredClothVertices = uncombinedVertices;
+ std::vector> &filteredClothFaces = uncombinedFaces;
+ std::vector 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_clothCollisionTriangles);
+ m_clothCollisionTriangles,
+ externalForces);
clothSimulator.setStiffness(componentClothStiffness(component));
clothSimulator.create();
- for (size_t i = 0; i < 400; ++i)
+ for (size_t i = 0; i < 350; ++i)
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 updateVertexIndices = [=](std::vector> &faces) {
@@ -1654,10 +1759,10 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
subIt += vertexStartIndex;
}
};
- updateVertexIndices(uncombinedFaces);
+ updateVertexIndices(filteredClothFaces);
- m_outcome->vertices.insert(m_outcome->vertices.end(), uncombinedVertices.begin(), uncombinedVertices.end());
- for (const auto &it: uncombinedFaces) {
+ m_outcome->vertices.insert(m_outcome->vertices.end(), filteredClothVertices.begin(), filteredClothVertices.end());
+ for (const auto &it: filteredClothFaces) {
if (4 == it.size()) {
m_outcome->triangles.push_back(std::vector {
it[0], it[1], it[2]
@@ -1669,14 +1774,13 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
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());
+ Q_ASSERT(uncombinedVertices.size() == filteredClothVertices.size());
for (size_t i = 0; i < uncombinedVertices.size(); ++i) {
const auto &source = uncombinedVertexSources[i];
- if (source.first.isNull())
- continue;
- m_outcome->nodeVertices.push_back(std::make_pair(uncombinedVertices[i], source));
+ m_outcome->nodeVertices.push_back(std::make_pair(filteredClothVertices[i], source));
}
return;
}
diff --git a/src/meshgenerator.h b/src/meshgenerator.h
index d5eda2b0..61cd1b4f 100644
--- a/src/meshgenerator.h
+++ b/src/meshgenerator.h
@@ -12,6 +12,7 @@
#include "combinemode.h"
#include "meshloader.h"
#include "componentlayer.h"
+#include "clothforce.h"
class GeneratedPart
{
@@ -24,6 +25,7 @@ public:
std::vector vertices;
std::vector> faces;
std::vector outcomeNodes;
+ std::vector, std::pair>> outcomeEdges;
std::vector>> outcomeNodeVertices;
std::vector previewVertices;
std::vector> previewTriangles;
@@ -43,6 +45,7 @@ public:
std::set> sharedQuadEdges;
std::set noneSeamVertices;
std::vector outcomeNodes;
+ std::vector, std::pair>> outcomeEdges;
std::vector>> outcomeNodeVertices;
std::vector outcomePaintMaps;
};
@@ -103,6 +106,7 @@ private:
quint64 m_id = 0;
std::vector m_clothCollisionVertices;
std::vector> m_clothCollisionTriangles;
+ std::vector> m_clothTargetNodes;
void collectParts();
bool checkIsComponentDirty(const QString &componentIdString);
@@ -130,6 +134,8 @@ private:
QString componentColorName(const std::map *component);
ComponentLayer componentLayer(const std::map *component);
float componentClothStiffness(const std::map *component);
+ ClothForce componentClothForce(const std::map *component);
+ float componentClothOffset(const std::map *component);
void collectUncombinedComponent(const QString &componentIdString);
void collectClothComponent(const QString &componentIdString);
void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector &cutTemplate);
@@ -141,6 +147,9 @@ private:
std::vector> *outputQuads,
std::vector> *outputTriangles,
std::vector>> *outputNodeVertices);
+ void buildClothTargetNodes(const std::vector &nodes,
+ const std::vector, std::pair>> &edges,
+ std::vector> *targetNodes);
};
#endif
diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp
index 52658ac8..d35cb1c6 100644
--- a/src/parttreewidget.cpp
+++ b/src/parttreewidget.cpp
@@ -246,8 +246,43 @@ void PartTreeWidget::showClothSettingMenu(const QPoint &pos, const QUuid &compon
clothStiffnessLayout->addWidget(clothStiffnessEraser);
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(&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;
mainLayout->addLayout(clothStiffnessLayout);
+ mainLayout->addLayout(clothOffsetLayout);
+ mainLayout->addLayout(clothForceLayout);
popup->setLayout(mainLayout);
diff --git a/src/parttreewidget.h b/src/parttreewidget.h
index e26940ed..bbfa0a81 100644
--- a/src/parttreewidget.h
+++ b/src/parttreewidget.h
@@ -42,6 +42,8 @@ signals:
void setPartColorState(QUuid partId, bool hasColor, QColor color);
void setComponentCombineMode(QUuid componentId, CombineMode combineMode);
void setComponentClothStiffness(QUuid componentId, float clothStiffness);
+ void setComponentClothForce(QUuid componentId, ClothForce force);
+ void setComponentClothOffset(QUuid componentId, float offset);
void hideDescendantComponents(QUuid componentId);
void showDescendantComponents(QUuid componentId);
void lockDescendantComponents(QUuid componentId);
diff --git a/src/preferenceswidget.cpp b/src/preferenceswidget.cpp
index f96516cf..4f359dc8 100644
--- a/src/preferenceswidget.cpp
+++ b/src/preferenceswidget.cpp
@@ -40,12 +40,14 @@ PreferencesWidget::PreferencesWidget(const Document *document, QWidget *parent)
});
connect(pickButton, &QPushButton::clicked, [=]() {
+ emit disableBackgroundBlur();
QColor color = QColorDialog::getColor(Preferences::instance().partColor(), this);
if (color.isValid()) {
Preferences::instance().setPartColor(color);
updatePickButtonColor();
raise();
}
+ emit enableBackgroundBlur();
});
QComboBox *combineModeSelectBox = new QComboBox;
diff --git a/src/preferenceswidget.h b/src/preferenceswidget.h
index b2230245..801a8948 100644
--- a/src/preferenceswidget.h
+++ b/src/preferenceswidget.h
@@ -8,6 +8,8 @@ class PreferencesWidget : public QDialog
Q_OBJECT
signals:
void enableWeld(bool enabled);
+ void enableBackgroundBlur();
+ void disableBackgroundBlur();
public:
PreferencesWidget(const Document *document, QWidget *parent=nullptr);
private:
diff --git a/src/projectfacestonodes.cpp b/src/projectfacestonodes.cpp
new file mode 100644
index 00000000..fdc9237a
--- /dev/null
+++ b/src/projectfacestonodes.cpp
@@ -0,0 +1,75 @@
+#include
+#include
+#include
+#include "projectfacestonodes.h"
+#include "util.h"
+
+class FacesToNodesProjector
+{
+public:
+ FacesToNodesProjector(const std::vector *vertices,
+ const std::vector> *faces,
+ const std::vector> *sourceNodes,
+ std::vector *faceSources) :
+ m_vertices(vertices),
+ m_faces(faces),
+ m_sourceNodes(sourceNodes),
+ m_faceSources(faceSources)
+ {
+ }
+ bool intersectionTest(const std::vector &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 &range) const
+ {
+ for (size_t i = range.begin(); i != range.end(); ++i) {
+ std::vector> 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 &first, const std::pair &second) {
+ return first.second < second.second;
+ })->first;
+ }
+ }
+private:
+ const std::vector *m_vertices = nullptr;
+ const std::vector> *m_faces = nullptr;
+ const std::vector> *m_sourceNodes = nullptr;
+ std::vector *m_faceSources = nullptr;
+};
+
+void projectFacesToNodes(const std::vector &vertices,
+ const std::vector> &faces,
+ const std::vector> &sourceNodes,
+ std::vector *faceSources)
+{
+ faceSources->resize(faces.size());
+ tbb::parallel_for(tbb::blocked_range(0, faces.size()),
+ FacesToNodesProjector(&vertices, &faces, &sourceNodes, faceSources));
+}
diff --git a/src/projectfacestonodes.h b/src/projectfacestonodes.h
new file mode 100644
index 00000000..9fbc1b2c
--- /dev/null
+++ b/src/projectfacestonodes.h
@@ -0,0 +1,11 @@
+#ifndef DUST3D_PROJECT_TRIANGLES_TO_NODES_H
+#define DUST3D_PROJECT_TRIANGLES_TO_NODES_H
+#include
+#include
+
+void projectFacesToNodes(const std::vector &vertices,
+ const std::vector> &faces,
+ const std::vector> &sourceNodes,
+ std::vector *faceSources);
+
+#endif
diff --git a/src/remesher.cpp b/src/remesher.cpp
index d2c214b3..00d40d62 100644
--- a/src/remesher.cpp
+++ b/src/remesher.cpp
@@ -1,6 +1,9 @@
#include
#include
+#include
#include "remesher.h"
+#include "util.h"
+#include "projectfacestonodes.h"
Remesher::Remesher()
{
@@ -43,6 +46,7 @@ void Remesher::remesh(float targetVertexMultiplyFactor)
}
std::vector inputTriangles;
inputTriangles.reserve(m_triangles.size());
+ float totalArea = 0.0f;
for (size_t i = 0; i < m_triangles.size(); ++i) {
const auto &triangle = m_triangles[i];
if (triangle.size() != 3)
@@ -52,6 +56,7 @@ void Remesher::remesh(float targetVertexMultiplyFactor)
triangle[1],
triangle[2]
}});
+ totalArea += areaOfTriangle(m_vertices[triangle[0]], m_vertices[triangle[1]], m_vertices[triangle[2]]);
}
const Dust3D_InstantMeshesVertex *resultVertices = nullptr;
size_t nResultVertices = 0;
@@ -61,7 +66,7 @@ void Remesher::remesh(float targetVertexMultiplyFactor)
size_t nResultQuads = 0;
Dust3D_instantMeshesRemesh(inputVertices.data(), inputVertices.size(),
inputTriangles.data(), inputTriangles.size(),
- (size_t)(inputVertices.size() * targetVertexMultiplyFactor),
+ (size_t)(targetVertexMultiplyFactor * 30 * std::sqrt(totalArea) / 0.02f),
&resultVertices,
&nResultVertices,
&resultTriangles,
@@ -100,27 +105,25 @@ void Remesher::setNodes(const std::vector> &nodes,
void Remesher::resolveSources()
{
- m_remeshedVertexSources.resize(m_remeshedVertices.size());
- std::vector nodeRadius2(m_nodes.size());
- for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) {
- nodeRadius2[nodeIndex] = std::pow(m_nodes[nodeIndex].second, 2);
+ QElapsedTimer timer;
+ timer.start();
+ std::vector 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> 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]++;
}
- for (size_t vertexIndex = 0; vertexIndex < m_remeshedVertices.size(); ++vertexIndex) {
- std::vector> 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 &first,
- const std::pair &second) {
- return first.first < second.first;
- });
- if (matches.empty())
- continue;
- m_remeshedVertexSources[vertexIndex] = m_sourceIds[matches[0].second];
+ m_remeshedVertexSources.resize(m_remeshedVertices.size());
+ for (const auto &it: vertexToNodeVotes) {
+ auto sourceIndex = std::max_element(it.second.begin(), it.second.end(), [](const std::pair &first, const std::pair &second) {
+ return first.second < second.second;
+ })->first;
+ m_remeshedVertexSources[it.first] = m_sourceIds[sourceIndex];
}
}