Add force and offset settings for cloth simulation
parent
8ffef700c4
commit
89d49d488d
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#include <QObject>
|
||||
#include "clothforce.h"
|
||||
|
||||
IMPL_ClothForceFromString
|
||||
IMPL_ClothForceToString
|
||||
IMPL_ClothForceToDispName
|
|
@ -0,0 +1,49 @@
|
|||
#ifndef DUST3D_CLOTH_FORCE_H
|
||||
#define DUST3D_CLOTH_FORCE_H
|
||||
#include <QString>
|
||||
|
||||
enum class ClothForce
|
||||
{
|
||||
Gravitational = 0,
|
||||
Centripetal,
|
||||
Count
|
||||
};
|
||||
ClothForce ClothForceFromString(const char *forceString);
|
||||
#define IMPL_ClothForceFromString \
|
||||
ClothForce ClothForceFromString(const char *forceString) \
|
||||
{ \
|
||||
QString force = forceString; \
|
||||
if (force == "Gravitational") \
|
||||
return ClothForce::Gravitational; \
|
||||
if (force == "Centripetal") \
|
||||
return ClothForce::Centripetal; \
|
||||
return ClothForce::Gravitational; \
|
||||
}
|
||||
const char *ClothForceToString(ClothForce force);
|
||||
#define IMPL_ClothForceToString \
|
||||
const char *ClothForceToString(ClothForce force) \
|
||||
{ \
|
||||
switch (force) { \
|
||||
case ClothForce::Gravitational: \
|
||||
return "Gravitational"; \
|
||||
case ClothForce::Centripetal: \
|
||||
return "Centripetal"; \
|
||||
default: \
|
||||
return "Gravitational"; \
|
||||
} \
|
||||
}
|
||||
QString ClothForceToDispName(ClothForce force);
|
||||
#define IMPL_ClothForceToDispName \
|
||||
QString ClothForceToDispName(ClothForce force) \
|
||||
{ \
|
||||
switch (force) { \
|
||||
case ClothForce::Gravitational: \
|
||||
return QObject::tr("Gravitational"); \
|
||||
case ClothForce::Centripetal: \
|
||||
return QObject::tr("Centripetal"); \
|
||||
default: \
|
||||
return QObject::tr("Gravitational"); \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif
|
|
@ -112,7 +112,8 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void fixPoints(CgPointFixNode *fixNode) {
|
||||
void fixPoints(CgPointFixNode *fixNode)
|
||||
{
|
||||
for (unsigned int i = 0; i < system->n_points; i++) {
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/blocked_range.h>
|
||||
#include <cmath>
|
||||
#include "projectfacestonodes.h"
|
||||
#include "util.h"
|
||||
|
||||
class FacesToNodesProjector
|
||||
{
|
||||
public:
|
||||
FacesToNodesProjector(const std::vector<QVector3D> *vertices,
|
||||
const std::vector<std::vector<size_t>> *faces,
|
||||
const std::vector<std::pair<QVector3D, float>> *sourceNodes,
|
||||
std::vector<size_t> *faceSources) :
|
||||
m_vertices(vertices),
|
||||
m_faces(faces),
|
||||
m_sourceNodes(sourceNodes),
|
||||
m_faceSources(faceSources)
|
||||
{
|
||||
}
|
||||
bool intersectionTest(const std::vector<size_t> &face,
|
||||
const QVector3D &nodePosition, float nodeRadius,
|
||||
float *distance2) const
|
||||
{
|
||||
QVector3D faceCenter;
|
||||
for (const auto &it: face) {
|
||||
faceCenter += (*m_vertices)[it];
|
||||
}
|
||||
if (face.size() > 0)
|
||||
faceCenter /= face.size();
|
||||
const auto &A = faceCenter;
|
||||
const auto &C = nodePosition;
|
||||
auto B = -polygonNormal(*m_vertices, face);
|
||||
auto a = QVector3D::dotProduct(B, B);
|
||||
auto b = 2.0f * QVector3D::dotProduct(B, A - C);
|
||||
const auto &r = nodeRadius;
|
||||
auto c = QVector3D::dotProduct(A - C, A - C) - r * r;
|
||||
if (b * b - 4 * a * c <= 0)
|
||||
return false;
|
||||
*distance2 = (faceCenter - nodePosition).lengthSquared();
|
||||
return true;
|
||||
}
|
||||
void operator()(const tbb::blocked_range<size_t> &range) const
|
||||
{
|
||||
for (size_t i = range.begin(); i != range.end(); ++i) {
|
||||
std::vector<std::pair<size_t, float>> distance2WithNodes;
|
||||
for (size_t j = 0; j < m_sourceNodes->size(); ++j) {
|
||||
const auto &node = (*m_sourceNodes)[j];
|
||||
float distance2 = 0.0f;
|
||||
if (!intersectionTest((*m_faces)[i], node.first, node.second, &distance2))
|
||||
continue;
|
||||
distance2WithNodes.push_back(std::make_pair(j, distance2));
|
||||
}
|
||||
if (distance2WithNodes.empty())
|
||||
continue;
|
||||
(*m_faceSources)[i] = std::min_element(distance2WithNodes.begin(), distance2WithNodes.end(), [](const std::pair<size_t, float> &first, const std::pair<size_t, float> &second) {
|
||||
return first.second < second.second;
|
||||
})->first;
|
||||
}
|
||||
}
|
||||
private:
|
||||
const std::vector<QVector3D> *m_vertices = nullptr;
|
||||
const std::vector<std::vector<size_t>> *m_faces = nullptr;
|
||||
const std::vector<std::pair<QVector3D, float>> *m_sourceNodes = nullptr;
|
||||
std::vector<size_t> *m_faceSources = nullptr;
|
||||
};
|
||||
|
||||
void projectFacesToNodes(const std::vector<QVector3D> &vertices,
|
||||
const std::vector<std::vector<size_t>> &faces,
|
||||
const std::vector<std::pair<QVector3D, float>> &sourceNodes,
|
||||
std::vector<size_t> *faceSources)
|
||||
{
|
||||
faceSources->resize(faces.size());
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, faces.size()),
|
||||
FacesToNodesProjector(&vertices, &faces, &sourceNodes, faceSources));
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef DUST3D_PROJECT_TRIANGLES_TO_NODES_H
|
||||
#define DUST3D_PROJECT_TRIANGLES_TO_NODES_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
|
||||
void projectFacesToNodes(const std::vector<QVector3D> &vertices,
|
||||
const std::vector<std::vector<size_t>> &faces,
|
||||
const std::vector<std::pair<QVector3D, float>> &sourceNodes,
|
||||
std::vector<size_t> *faceSources);
|
||||
|
||||
#endif
|
|
@ -1,6 +1,9 @@
|
|||
#include <instant-meshes-api.h>
|
||||
#include <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];
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue