Add force and offset settings for cloth simulation

master
Jeremy Hu 2020-01-12 22:18:44 +09:30
parent 8ffef700c4
commit 89d49d488d
18 changed files with 454 additions and 48 deletions

View File

@ -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

View File

@ -756,6 +756,14 @@ Tips:
<source>Stiffness</source>
<translation></translation>
</message>
<message>
<source>Force</source>
<translation></translation>
</message>
<message>
<source>Offset</source>
<translation></translation>
</message>
</context>
<context>
<name>PartWidget</name>
@ -1086,6 +1094,14 @@ Tips:
<source>Cloth</source>
<translation></translation>
</message>
<message>
<source>Gravitational</source>
<translation></translation>
</message>
<message>
<source>Centripetal</source>
<translation></translation>
</message>
</context>
<context>
<name>RigWidget</name>

6
src/clothforce.cpp Normal file
View File

@ -0,0 +1,6 @@
#include <QObject>
#include "clothforce.h"
IMPL_ClothForceFromString
IMPL_ClothForceToString
IMPL_ClothForceToDispName

49
src/clothforce.h Normal file
View File

@ -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

View File

@ -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<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,
const std::vector<std::vector<size_t>> &faces,
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_faces(faces),
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];
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);

View File

@ -18,7 +18,8 @@ public:
ClothSimulator(const std::vector<QVector3D> &vertices,
const std::vector<std::vector<size_t>> &faces,
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();
void setStiffness(float stiffness);
void create();
@ -29,10 +30,12 @@ private:
std::vector<std::vector<size_t>> m_faces;
std::vector<QVector3D> m_collisionVertices;
std::vector<std::vector<size_t>> m_collisionTriangles;
std::vector<QVector3D> m_externalForces;
std::vector<float> m_clothPointBuffer;
std::vector<size_t> m_clothPointSources;
std::vector<std::pair<size_t, size_t>> m_clothSprings;
float m_stiffness = 1.0f;
QVector3D m_offset;
mass_spring_system *m_massSpringSystem = nullptr;
MassSpringSolver *m_massSpringSolver = nullptr;
CgRootNode *m_rootNode = nullptr;

View File

@ -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<QUuid> &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);

View File

@ -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<QUuid> 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<QUuid> 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();

View File

@ -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();

View File

@ -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<QString, QString> *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<QString, QString> *c
float MeshGenerator::componentClothStiffness(const std::map<QString, QString> *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<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 *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<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 *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<QVector3D> uncombinedVertices;
std::vector<std::vector<size_t>> uncombinedOriginalFaces;
std::vector<std::vector<size_t>> 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<PositionKey, std::pair<QUuid, QUuid>> positionMap;
std::pair<QUuid, QUuid> defaultSource;
@ -1637,15 +1720,37 @@ void MeshGenerator::collectClothComponent(const QString &componentIdString)
uncombinedVertexSources[i] = findSource->second;
}
ClothSimulator clothSimulator(uncombinedVertices,
uncombinedFaces,
std::vector<QVector3D> &filteredClothVertices = uncombinedVertices;
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_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<std::vector<size_t>> &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<size_t> {
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;
}

View File

@ -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<QVector3D> vertices;
std::vector<std::vector<size_t>> faces;
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<QVector3D> previewVertices;
std::vector<std::vector<size_t>> previewTriangles;
@ -43,6 +45,7 @@ public:
std::set<std::pair<PositionKey, PositionKey>> sharedQuadEdges;
std::set<PositionKey> noneSeamVertices;
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<OutcomePaintMap> outcomePaintMaps;
};
@ -103,6 +106,7 @@ private:
quint64 m_id = 0;
std::vector<QVector3D> m_clothCollisionVertices;
std::vector<std::vector<size_t>> m_clothCollisionTriangles;
std::vector<std::pair<QVector3D, float>> m_clothTargetNodes;
void collectParts();
bool checkIsComponentDirty(const QString &componentIdString);
@ -130,6 +134,8 @@ private:
QString componentColorName(const std::map<QString, QString> *component);
ComponentLayer componentLayer(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 collectClothComponent(const QString &componentIdString);
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>> *outputTriangles,
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

View File

@ -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<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;
mainLayout->addLayout(clothStiffnessLayout);
mainLayout->addLayout(clothOffsetLayout);
mainLayout->addLayout(clothForceLayout);
popup->setLayout(mainLayout);

View File

@ -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);

View File

@ -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;

View File

@ -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:

View File

@ -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));
}

11
src/projectfacestonodes.h Normal file
View File

@ -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

View File

@ -1,6 +1,9 @@
#include <instant-meshes-api.h>
#include <cmath>
#include <QElapsedTimer>
#include "remesher.h"
#include "util.h"
#include "projectfacestonodes.h"
Remesher::Remesher()
{
@ -43,6 +46,7 @@ void Remesher::remesh(float targetVertexMultiplyFactor)
}
std::vector<Dust3D_InstantMeshesTriangle> 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<std::pair<QVector3D, float>> &nodes,
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());
std::vector<float> nodeRadius2(m_nodes.size());
for (size_t nodeIndex = 0; nodeIndex < m_nodes.size(); ++nodeIndex) {
nodeRadius2[nodeIndex] = std::pow(m_nodes[nodeIndex].second, 2);
}
for (size_t vertexIndex = 0; vertexIndex < m_remeshedVertices.size(); ++vertexIndex) {
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];
for (const auto &it: vertexToNodeVotes) {
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) {
return first.second < second.second;
})->first;
m_remeshedVertexSources[it.first] = m_sourceIds[sourceIndex];
}
}