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: Stiffness 硬度 + + Force + + + + Offset + 偏移 + PartWidget @@ -1086,6 +1094,14 @@ Tips: Cloth 衣服 + + Gravitational + 重力 + + + Centripetal + 向心力 + 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]; } }