Remove component layer
parent
7817cde381
commit
62418b9340
21
dust3d.pro
21
dust3d.pro
|
@ -344,9 +344,6 @@ HEADERS += src/combinemode.h
|
|||
SOURCES += src/polycount.cpp
|
||||
HEADERS += src/polycount.h
|
||||
|
||||
SOURCES += src/cutdocument.cpp
|
||||
HEADERS += src/cutdocument.h
|
||||
|
||||
SOURCES += src/cutface.cpp
|
||||
HEADERS += src/cutface.h
|
||||
|
||||
|
@ -403,12 +400,6 @@ HEADERS += src/boundingboxmesh.h
|
|||
SOURCES += src/regionfiller.cpp
|
||||
HEADERS += src/regionfiller.h
|
||||
|
||||
SOURCES += src/cyclefinder.cpp
|
||||
HEADERS += src/cyclefinder.h
|
||||
|
||||
SOURCES += src/shortestpath.cpp
|
||||
HEADERS += src/shortestpath.h
|
||||
|
||||
SOURCES += src/meshwrapper.cpp
|
||||
HEADERS += src/meshwrapper.h
|
||||
|
||||
|
@ -442,24 +433,12 @@ HEADERS += src/booleanmesh.h
|
|||
SOURCES += src/remesher.cpp
|
||||
HEADERS += src/remesher.h
|
||||
|
||||
SOURCES += src/clothsimulator.cpp
|
||||
HEADERS += src/clothsimulator.h
|
||||
|
||||
SOURCES += src/componentlayer.cpp
|
||||
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/simulateclothmeshes.cpp
|
||||
HEADERS += src/simulateclothmeshes.h
|
||||
|
||||
SOURCES += src/ddsfile.cpp
|
||||
HEADERS += src/ddsfile.h
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
#include <QObject>
|
||||
#include "clothforce.h"
|
||||
|
||||
IMPL_ClothForceFromString
|
||||
IMPL_ClothForceToString
|
||||
IMPL_ClothForceToDispName
|
|
@ -1,49 +0,0 @@
|
|||
#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
|
|
@ -1,290 +0,0 @@
|
|||
#include <MassSpringSolver.h>
|
||||
#include <set>
|
||||
#include <CGAL/Simple_cartesian.h>
|
||||
#include <CGAL/AABB_tree.h>
|
||||
#include <CGAL/AABB_traits.h>
|
||||
#include <CGAL/Polyhedron_3.h>
|
||||
#include <CGAL/Polyhedron_incremental_builder_3.h>
|
||||
#include <CGAL/boost/graph/graph_traits_Polyhedron_3.h>
|
||||
#include <CGAL/AABB_face_graph_triangle_primitive.h>
|
||||
#include <CGAL/algorithm.h>
|
||||
#include <CGAL/Side_of_triangle_mesh.h>
|
||||
#include "clothsimulator.h"
|
||||
#include "booleanmesh.h"
|
||||
|
||||
typedef CGAL::Simple_cartesian<double> K;
|
||||
typedef K::Point_3 Point;
|
||||
typedef K::Triangle_3 Triangle;
|
||||
typedef CGAL::Polyhedron_3<K> Polyhedron;
|
||||
typedef Polyhedron::HalfedgeDS HalfedgeDS;
|
||||
typedef CGAL::AABB_face_graph_triangle_primitive<Polyhedron> Primitive;
|
||||
typedef CGAL::AABB_traits<K, Primitive> Traits;
|
||||
typedef CGAL::AABB_tree<Traits> Tree;
|
||||
typedef CGAL::Side_of_triangle_mesh<Polyhedron, K> Point_inside;
|
||||
|
||||
template <class HDS>
|
||||
class Build_mesh : public CGAL::Modifier_base<HDS> {
|
||||
public:
|
||||
Build_mesh(const std::vector<QVector3D> *vertices,
|
||||
const std::vector<std::vector<size_t>> *faces) :
|
||||
m_vertices(vertices),
|
||||
m_faces(faces)
|
||||
{
|
||||
};
|
||||
void operator()(HDS& hds)
|
||||
{
|
||||
// Postcondition: hds is a valid polyhedral surface.
|
||||
CGAL::Polyhedron_incremental_builder_3<HDS> B(hds, false);
|
||||
B.begin_surface(m_vertices->size(), m_faces->size());
|
||||
typedef typename HDS::Vertex Vertex;
|
||||
typedef typename Vertex::Point Point;
|
||||
for (const auto &it: *m_vertices)
|
||||
B.add_vertex(Point(it.x(), it.y(), it.z()));
|
||||
for (const auto &it: *m_faces) {
|
||||
B.begin_facet();
|
||||
B.add_vertex_to_facet(it[0]);
|
||||
B.add_vertex_to_facet(it[1]);
|
||||
B.add_vertex_to_facet(it[2]);
|
||||
B.end_facet();
|
||||
}
|
||||
B.end_surface();
|
||||
};
|
||||
private:
|
||||
const std::vector<QVector3D> *m_vertices = nullptr;
|
||||
const std::vector<std::vector<size_t>> *m_faces = nullptr;
|
||||
};
|
||||
|
||||
// System parameters
|
||||
//namespace SystemParam {
|
||||
// static const int n = 61; // must be odd, n * n = n_vertices | 61
|
||||
// static const float h = 0.001f; // time step, smaller for better results | 0.008f = 0.016f/2
|
||||
// static const float m = 0.25f / (n * n); // point mass | 0.25f
|
||||
// static const float a = 0.993f; // damping, close to 1.0 | 0.993f
|
||||
// static const float g = 9.8f * m; // gravitational force | 9.8f
|
||||
//}
|
||||
|
||||
// Point - mesh collision node
|
||||
class CgMeshCollisionNode : public CgPointNode {
|
||||
private:
|
||||
Tree *m_aabbTree = nullptr;
|
||||
Point_inside *m_insideTester = nullptr;
|
||||
Polyhedron m_polyhedron;
|
||||
public:
|
||||
CgMeshCollisionNode(mass_spring_system *system, float *vbuff,
|
||||
const std::vector<QVector3D> &collisionVertices,
|
||||
const std::vector<std::vector<size_t>> &collisionTriangles) :
|
||||
CgPointNode(system, vbuff)
|
||||
{
|
||||
if (!collisionTriangles.empty()) {
|
||||
Build_mesh<HalfedgeDS> mesh(&collisionVertices, &collisionTriangles);
|
||||
m_polyhedron.delegate(mesh);
|
||||
m_aabbTree = new Tree(faces(m_polyhedron).first, faces(m_polyhedron).second, m_polyhedron);
|
||||
m_aabbTree->accelerate_distance_queries();
|
||||
m_insideTester = new Point_inside(*m_aabbTree);
|
||||
}
|
||||
}
|
||||
|
||||
~CgMeshCollisionNode()
|
||||
{
|
||||
delete m_insideTester;
|
||||
delete m_aabbTree;
|
||||
};
|
||||
|
||||
bool query(unsigned int i) const
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
void satisfy()
|
||||
{
|
||||
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) {
|
||||
Point closestPoint = m_aabbTree->closest_point(point);
|
||||
vbuff[offset + 0] = closestPoint.x();
|
||||
vbuff[offset + 1] = closestPoint.y();
|
||||
vbuff[offset + 2] = closestPoint.z();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void fixPoints(CgPointFixNode *fixNode)
|
||||
{
|
||||
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) {
|
||||
fixNode->fixPoint(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<QVector3D> &externalForces) :
|
||||
m_vertices(vertices),
|
||||
m_faces(faces),
|
||||
m_collisionVertices(collisionVertices),
|
||||
m_collisionTriangles(collisionTriangles),
|
||||
m_externalForces(externalForces)
|
||||
{
|
||||
}
|
||||
|
||||
ClothSimulator::~ClothSimulator()
|
||||
{
|
||||
delete m_massSpringSystem;
|
||||
delete m_massSpringSolver;
|
||||
delete m_rootNode;
|
||||
delete m_deformationNode;
|
||||
delete m_meshCollisionNode;
|
||||
delete m_fixNode;
|
||||
}
|
||||
|
||||
void ClothSimulator::setStiffness(float stiffness)
|
||||
{
|
||||
m_stiffness = 1.0f + 5.0f * stiffness;
|
||||
}
|
||||
|
||||
void ClothSimulator::convertMeshToCloth()
|
||||
{
|
||||
m_clothPointSources.reserve(m_vertices.size());
|
||||
m_clothPointBuffer.reserve(m_vertices.size() * 3);
|
||||
|
||||
std::map<size_t, size_t> oldVertexToNewMap;
|
||||
auto addPoint = [&](size_t index) {
|
||||
auto findNew = oldVertexToNewMap.find(index);
|
||||
if (findNew != oldVertexToNewMap.end())
|
||||
return findNew->second;
|
||||
const auto &position = m_vertices[index];
|
||||
m_clothPointBuffer.push_back(position.x());
|
||||
m_clothPointBuffer.push_back(position.y());
|
||||
m_clothPointBuffer.push_back(position.z());
|
||||
size_t newIndex = m_clothPointSources.size();
|
||||
m_clothPointSources.push_back(index);
|
||||
oldVertexToNewMap.insert({index, newIndex});
|
||||
return newIndex;
|
||||
};
|
||||
|
||||
std::set<std::pair<size_t, size_t>> oldEdges;
|
||||
for (const auto &it: m_faces) {
|
||||
for (size_t i = 0; i < it.size(); ++i) {
|
||||
size_t j = (i + 1) % it.size();
|
||||
if (oldEdges.find(std::make_pair(it[i], it[j])) != oldEdges.end())
|
||||
continue;
|
||||
m_clothSprings.push_back({addPoint(it[i]), addPoint(it[j])});
|
||||
oldEdges.insert({it[i], it[j]});
|
||||
oldEdges.insert({it[j], it[i]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClothSimulator::getCurrentVertices(std::vector<QVector3D> *currentVertices)
|
||||
{
|
||||
*currentVertices = m_vertices;
|
||||
for (size_t newIndex = 0; newIndex < m_clothPointSources.size(); ++newIndex) {
|
||||
size_t oldIndex = m_clothPointSources[newIndex];
|
||||
auto offset = newIndex * 3;
|
||||
(*currentVertices)[oldIndex] = QVector3D(m_clothPointBuffer[offset + 0],
|
||||
m_clothPointBuffer[offset + 1],
|
||||
m_clothPointBuffer[offset + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
void ClothSimulator::step()
|
||||
{
|
||||
if (nullptr == m_massSpringSolver)
|
||||
return;
|
||||
|
||||
m_massSpringSolver->solve(5);
|
||||
m_massSpringSolver->solve(5);
|
||||
|
||||
CgSatisfyVisitor visitor;
|
||||
visitor.satisfy(*m_rootNode);
|
||||
}
|
||||
|
||||
void ClothSimulator::create()
|
||||
{
|
||||
convertMeshToCloth();
|
||||
|
||||
if (m_clothPointSources.empty())
|
||||
return;
|
||||
|
||||
float mass = 0.25f / m_clothPointSources.size();
|
||||
float gravitationalForce = 9.8f * mass;
|
||||
float damping = 0.993f; // damping, close to 1.0 | 0.993f;
|
||||
float timeStep = 0.001f; //smaller for better results | 0.008f = 0.016f/2;
|
||||
|
||||
mass_spring_system::VectorXf masses(mass * mass_spring_system::VectorXf::Ones((unsigned int)m_clothSprings.size()));
|
||||
|
||||
mass_spring_system::EdgeList springList(m_clothSprings.size());
|
||||
mass_spring_system::VectorXf restLengths(m_clothSprings.size());
|
||||
mass_spring_system::VectorXf stiffnesses(m_clothSprings.size());
|
||||
|
||||
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() * 0.8;
|
||||
stiffnesses[i] = m_stiffness;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
m_massSpringSolver = new MassSpringSolver(m_massSpringSystem, m_clothPointBuffer.data());
|
||||
|
||||
// deformation constraint parameters
|
||||
const float tauc = 0.12f; // critical spring deformation | 0.12f
|
||||
const unsigned int deformIter = 15; // number of iterations | 15
|
||||
|
||||
std::vector<unsigned int> structSprintIndexList;
|
||||
structSprintIndexList.reserve(springList.size());
|
||||
m_deformationNode =
|
||||
new CgSpringDeformationNode(m_massSpringSystem, m_clothPointBuffer.data(), tauc, deformIter);
|
||||
m_deformationNode->addSprings(structSprintIndexList);
|
||||
|
||||
m_rootNode = new CgRootNode(m_massSpringSystem, m_clothPointBuffer.data());
|
||||
m_rootNode->addChild(m_deformationNode);
|
||||
|
||||
m_meshCollisionNode = new CgMeshCollisionNode(m_massSpringSystem, m_clothPointBuffer.data(),
|
||||
m_collisionVertices,
|
||||
m_collisionTriangles);
|
||||
|
||||
m_fixNode = new CgPointFixNode(m_massSpringSystem, m_clothPointBuffer.data());
|
||||
m_meshCollisionNode->fixPoints(m_fixNode);
|
||||
m_deformationNode->addChild(m_fixNode);
|
||||
|
||||
m_rootNode->addChild(m_meshCollisionNode);
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
#ifndef DUST3D_CLOTH_SIMULATOR_H
|
||||
#define DUST3D_CLOTH_SIMULATOR_H
|
||||
#include <QObject>
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
|
||||
struct mass_spring_system;
|
||||
class MassSpringSolver;
|
||||
class CgRootNode;
|
||||
class CgSpringDeformationNode;
|
||||
class CgMeshCollisionNode;
|
||||
class CgPointFixNode;
|
||||
|
||||
class ClothSimulator : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
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<QVector3D> &externalForces);
|
||||
~ClothSimulator();
|
||||
void setStiffness(float stiffness);
|
||||
void create();
|
||||
void step();
|
||||
void getCurrentVertices(std::vector<QVector3D> *currentVertices);
|
||||
private:
|
||||
std::vector<QVector3D> m_vertices;
|
||||
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;
|
||||
CgSpringDeformationNode *m_deformationNode = nullptr;
|
||||
CgMeshCollisionNode *m_meshCollisionNode = nullptr;
|
||||
CgPointFixNode *m_fixNode = nullptr;
|
||||
void convertMeshToCloth();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,6 +0,0 @@
|
|||
#include <QObject>
|
||||
#include "componentlayer.h"
|
||||
|
||||
IMPL_ComponentLayerToString
|
||||
IMPL_ComponentLayerFromString
|
||||
IMPL_ComponentLayerToDispName
|
|
@ -1,49 +0,0 @@
|
|||
#ifndef DUST3D_COMPONENT_LAYER_H
|
||||
#define DUST3D_COMPONENT_LAYER_H
|
||||
#include <QString>
|
||||
|
||||
enum class ComponentLayer
|
||||
{
|
||||
Body = 0,
|
||||
Cloth,
|
||||
Count
|
||||
};
|
||||
ComponentLayer ComponentLayerFromString(const char *layerString);
|
||||
#define IMPL_ComponentLayerFromString \
|
||||
ComponentLayer ComponentLayerFromString(const char *layerString) \
|
||||
{ \
|
||||
QString layer = layerString; \
|
||||
if (layer == "Body") \
|
||||
return ComponentLayer::Body; \
|
||||
if (layer == "Cloth") \
|
||||
return ComponentLayer::Cloth; \
|
||||
return ComponentLayer::Body; \
|
||||
}
|
||||
const char *ComponentLayerToString(ComponentLayer layer);
|
||||
#define IMPL_ComponentLayerToString \
|
||||
const char *ComponentLayerToString(ComponentLayer layer) \
|
||||
{ \
|
||||
switch (layer) { \
|
||||
case ComponentLayer::Body: \
|
||||
return "Body"; \
|
||||
case ComponentLayer::Cloth: \
|
||||
return "Cloth"; \
|
||||
default: \
|
||||
return "Body"; \
|
||||
} \
|
||||
}
|
||||
QString ComponentLayerToDispName(ComponentLayer layer);
|
||||
#define IMPL_ComponentLayerToDispName \
|
||||
QString ComponentLayerToDispName(ComponentLayer layer) \
|
||||
{ \
|
||||
switch (layer) { \
|
||||
case ComponentLayer::Body: \
|
||||
return QObject::tr("Body"); \
|
||||
case ComponentLayer::Cloth: \
|
||||
return QObject::tr("Cloth"); \
|
||||
default: \
|
||||
return QObject::tr("Body"); \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,201 +0,0 @@
|
|||
#include <QDebug>
|
||||
#include <QApplication>
|
||||
#include "cutdocument.h"
|
||||
|
||||
const float CutDocument::m_nodeRadius = 0.05;
|
||||
const float CutDocument::m_nodeScaleFactor = 0.5;
|
||||
|
||||
bool CutDocument::hasPastableNodesInClipboard() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CutDocument::originSettled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CutDocument::isNodeEditable(QUuid nodeId) const
|
||||
{
|
||||
Q_UNUSED(nodeId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CutDocument::isEdgeEditable(QUuid edgeId) const
|
||||
{
|
||||
Q_UNUSED(edgeId);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CutDocument::copyNodes(std::set<QUuid> nodeIdSet) const
|
||||
{
|
||||
Q_UNUSED(nodeIdSet);
|
||||
}
|
||||
|
||||
void CutDocument::saveHistoryItem()
|
||||
{
|
||||
CutHistoryItem item;
|
||||
toCutTemplate(item.cutTemplate);
|
||||
m_undoItems.push_back(item);
|
||||
}
|
||||
|
||||
bool CutDocument::undoable() const
|
||||
{
|
||||
return m_undoItems.size() >= 2;
|
||||
}
|
||||
|
||||
bool CutDocument::redoable() const
|
||||
{
|
||||
return !m_redoItems.empty();
|
||||
}
|
||||
|
||||
void CutDocument::undo()
|
||||
{
|
||||
if (!undoable())
|
||||
return;
|
||||
m_redoItems.push_back(m_undoItems.back());
|
||||
m_undoItems.pop_back();
|
||||
const auto &item = m_undoItems.back();
|
||||
fromCutTemplate(item.cutTemplate);
|
||||
}
|
||||
|
||||
void CutDocument::redo()
|
||||
{
|
||||
if (m_redoItems.empty())
|
||||
return;
|
||||
m_undoItems.push_back(m_redoItems.back());
|
||||
const auto &item = m_redoItems.back();
|
||||
fromCutTemplate(item.cutTemplate);
|
||||
m_redoItems.pop_back();
|
||||
}
|
||||
|
||||
void CutDocument::paste()
|
||||
{
|
||||
// void
|
||||
}
|
||||
|
||||
void CutDocument::reset()
|
||||
{
|
||||
nodeMap.clear();
|
||||
edgeMap.clear();
|
||||
partMap.clear();
|
||||
m_cutNodeIds.clear();
|
||||
emit cleanup();
|
||||
emit cutTemplateChanged();
|
||||
}
|
||||
|
||||
void CutDocument::clearHistories()
|
||||
{
|
||||
m_undoItems.clear();
|
||||
m_redoItems.clear();
|
||||
}
|
||||
|
||||
void CutDocument::toCutTemplate(std::vector<QVector2D> &cutTemplate)
|
||||
{
|
||||
for (const auto &nodeId: m_cutNodeIds) {
|
||||
auto findNode = nodeMap.find(nodeId);
|
||||
if (findNode == nodeMap.end())
|
||||
continue;
|
||||
QVector2D position = nodeToCutPosition({findNode->second.getX(),
|
||||
findNode->second.getY(),
|
||||
findNode->second.getZ()
|
||||
});
|
||||
cutTemplate.push_back(position);
|
||||
}
|
||||
}
|
||||
|
||||
QVector2D CutDocument::nodeToCutPosition(const QVector3D &nodePosition)
|
||||
{
|
||||
return {(nodePosition.x() * 2 - (float)1) / m_nodeScaleFactor,
|
||||
(nodePosition.y() * 2 - (float)1) / m_nodeScaleFactor};
|
||||
}
|
||||
|
||||
QVector3D CutDocument::cutToNodePosition(const QVector2D &cutPosition)
|
||||
{
|
||||
return {(cutPosition.x() * m_nodeScaleFactor + 1) * (float)0.5,
|
||||
(cutPosition.y() * m_nodeScaleFactor + 1) * (float)0.5,
|
||||
(float)0};
|
||||
}
|
||||
|
||||
void CutDocument::fromCutTemplate(const std::vector<QVector2D> &cutTemplate)
|
||||
{
|
||||
reset();
|
||||
|
||||
std::set<QUuid> newAddedNodeIds;
|
||||
std::set<QUuid> newAddedEdgeIds;
|
||||
|
||||
m_partId = QUuid::createUuid();
|
||||
auto &part = partMap[m_partId];
|
||||
part.id = m_partId;
|
||||
|
||||
for (const auto &position: cutTemplate) {
|
||||
SkeletonNode node;
|
||||
node.partId = m_partId;
|
||||
node.id = QUuid::createUuid();
|
||||
node.setRadius(m_nodeRadius);
|
||||
auto nodePosition = cutToNodePosition(position);
|
||||
node.setX(nodePosition.x());
|
||||
node.setY(nodePosition.y());
|
||||
node.setZ(nodePosition.z());
|
||||
nodeMap[node.id] = node;
|
||||
newAddedNodeIds.insert(node.id);
|
||||
m_cutNodeIds.push_back(node.id);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_cutNodeIds.size(); ++i) {
|
||||
size_t j = (i + 1) % m_cutNodeIds.size();
|
||||
const QUuid &firstNodeId = m_cutNodeIds[i];
|
||||
const QUuid &secondNodeId = m_cutNodeIds[j];
|
||||
|
||||
SkeletonEdge edge;
|
||||
edge.partId = m_partId;
|
||||
edge.id = QUuid::createUuid();
|
||||
edge.nodeIds.push_back(firstNodeId);
|
||||
edge.nodeIds.push_back(secondNodeId);
|
||||
edgeMap[edge.id] = edge;
|
||||
newAddedEdgeIds.insert(edge.id);
|
||||
nodeMap[firstNodeId].edgeIds.push_back(edge.id);
|
||||
nodeMap[secondNodeId].edgeIds.push_back(edge.id);
|
||||
}
|
||||
|
||||
for (const auto &nodeIt: newAddedNodeIds) {
|
||||
emit nodeAdded(nodeIt);
|
||||
}
|
||||
for (const auto &edgeIt: newAddedEdgeIds) {
|
||||
emit edgeAdded(edgeIt);
|
||||
}
|
||||
|
||||
emit cutTemplateChanged();
|
||||
}
|
||||
|
||||
void CutDocument::moveNodeBy(QUuid nodeId, float x, float y, float z)
|
||||
{
|
||||
auto it = nodeMap.find(nodeId);
|
||||
if (it == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
it->second.addX(x);
|
||||
it->second.addY(y);
|
||||
it->second.addZ(z);
|
||||
emit nodeOriginChanged(it->first);
|
||||
emit cutTemplateChanged();
|
||||
}
|
||||
|
||||
void CutDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z)
|
||||
{
|
||||
auto it = nodeMap.find(nodeId);
|
||||
if (it == nodeMap.end()) {
|
||||
qDebug() << "Find node failed:" << nodeId;
|
||||
return;
|
||||
}
|
||||
it->second.setX(x);
|
||||
it->second.setY(y);
|
||||
it->second.setZ(z);
|
||||
auto part = partMap.find(it->second.partId);
|
||||
if (part != partMap.end())
|
||||
part->second.dirty = true;
|
||||
emit nodeOriginChanged(nodeId);
|
||||
emit cutTemplateChanged();
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
#ifndef DUST3D_CUT_DOCUMENT_H
|
||||
#define DUST3D_CUT_DOCUMENT_H
|
||||
#include <deque>
|
||||
#include <QVector2D>
|
||||
#include "skeletondocument.h"
|
||||
|
||||
struct CutHistoryItem
|
||||
{
|
||||
std::vector<QVector2D> cutTemplate;
|
||||
};
|
||||
|
||||
class CutDocument : public SkeletonDocument
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void cleanup();
|
||||
void nodeAdded(QUuid nodeId);
|
||||
void edgeAdded(QUuid edgeId);
|
||||
void nodeOriginChanged(QUuid nodeId);
|
||||
void cutTemplateChanged();
|
||||
|
||||
public:
|
||||
bool undoable() const override;
|
||||
bool redoable() const override;
|
||||
bool hasPastableNodesInClipboard() const override;
|
||||
bool originSettled() const override;
|
||||
bool isNodeEditable(QUuid nodeId) const override;
|
||||
bool isEdgeEditable(QUuid edgeId) const override;
|
||||
void copyNodes(std::set<QUuid> nodeIdSet) const override;
|
||||
|
||||
void reset();
|
||||
void toCutTemplate(std::vector<QVector2D> &cutTemplate);
|
||||
void fromCutTemplate(const std::vector<QVector2D> &cutTemplate);
|
||||
|
||||
public slots:
|
||||
void saveHistoryItem();
|
||||
void clearHistories();
|
||||
void undo() override;
|
||||
void redo() override;
|
||||
void paste() override;
|
||||
|
||||
void moveNodeBy(QUuid nodeId, float x, float y, float z);
|
||||
void setNodeOrigin(QUuid nodeId, float x, float y, float z);
|
||||
|
||||
public:
|
||||
static const float m_nodeRadius;
|
||||
static const float m_nodeScaleFactor;
|
||||
|
||||
private:
|
||||
std::deque<CutHistoryItem> m_undoItems;
|
||||
std::deque<CutHistoryItem> m_redoItems;
|
||||
std::vector<QUuid> m_cutNodeIds;
|
||||
QUuid m_partId;
|
||||
|
||||
QVector2D nodeToCutPosition(const QVector3D &nodePosition);
|
||||
QVector3D cutToNodePosition(const QVector2D &cutPosition);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
#include <QString>
|
||||
#include <vector>
|
||||
#include <QDebug>
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include "cyclefinder.h"
|
||||
#include "shortestpath.h"
|
||||
|
||||
//
|
||||
// The cycle finder implement the following method:
|
||||
// 1. Remove edge
|
||||
// 2. Find shortest alternative path between the removed edge
|
||||
// https://www.geeksforgeeks.org/find-minimum-weight-cycle-undirected-graph/
|
||||
//
|
||||
|
||||
CycleFinder::CycleFinder(const std::vector<QVector3D> &nodePositions,
|
||||
const std::vector<std::pair<size_t, size_t>> &edges) :
|
||||
m_nodeNum(nodePositions.size()),
|
||||
m_nodePositions(nodePositions),
|
||||
m_edges(edges)
|
||||
{
|
||||
}
|
||||
|
||||
void CycleFinder::prepareWeights()
|
||||
{
|
||||
m_edgeLengths.resize(m_edges.size(), 1);
|
||||
for (size_t i = 0; i < m_edges.size(); ++i) {
|
||||
const auto &edge = m_edges[i];
|
||||
auto length = m_nodePositions[edge.first].distanceToPoint(
|
||||
m_nodePositions[edge.second]) * 10000;
|
||||
m_edgeLengths[i] = length;
|
||||
m_edgeLengthMap.insert({std::make_pair(edge.first, edge.second), length});
|
||||
m_edgeLengthMap.insert({std::make_pair(edge.second, edge.first), length});
|
||||
}
|
||||
}
|
||||
|
||||
bool CycleFinder::validateCycleByFlatness(const std::vector<size_t> &cycle)
|
||||
{
|
||||
// Validate cycle by mesaure the flatness of the face
|
||||
// Flatness = Average variation of corner normals
|
||||
if (cycle.empty())
|
||||
return false;
|
||||
std::vector<QVector3D> normals;
|
||||
for (size_t i = 0; i < cycle.size(); ++i) {
|
||||
size_t h = (i + cycle.size() - 1) % cycle.size();
|
||||
size_t j = (i + 1) % cycle.size();
|
||||
QVector3D vh = m_nodePositions[cycle[h]];
|
||||
QVector3D vi = m_nodePositions[cycle[i]];
|
||||
QVector3D vj = m_nodePositions[cycle[j]];
|
||||
if (QVector3D::dotProduct((vj - vi).normalized(),
|
||||
(vi - vh).normalized()) <= 0.966) { // 15 degrees
|
||||
auto vertexNormal = QVector3D::normal(vj - vi, vh - vi);
|
||||
normals.push_back(vertexNormal);
|
||||
}
|
||||
}
|
||||
if (normals.empty())
|
||||
return false;
|
||||
float sumOfDistance = 0;
|
||||
for (size_t i = 0; i < normals.size(); ++i) {
|
||||
size_t j = (i + 1) % normals.size();
|
||||
sumOfDistance += (normals[i] - normals[j]).length();
|
||||
}
|
||||
float flatness = sumOfDistance / normals.size();
|
||||
if (flatness >= m_invalidFlatness) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int CycleFinder::calculateCycleLength(const std::vector<size_t> &cycle)
|
||||
{
|
||||
float cycleLength = 0;
|
||||
for (size_t i = 0; i < cycle.size(); ++i) {
|
||||
size_t j = (i + 1) % cycle.size();
|
||||
auto edge = std::make_pair(cycle[i], cycle[j]);
|
||||
auto findEdgeLength = m_edgeLengthMap.find(edge);
|
||||
if (findEdgeLength == m_edgeLengthMap.end())
|
||||
continue;
|
||||
cycleLength += findEdgeLength->second;
|
||||
}
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
void CycleFinder::find()
|
||||
{
|
||||
prepareWeights();
|
||||
|
||||
if (m_edges.empty())
|
||||
return;
|
||||
|
||||
std::queue<std::pair<size_t, size_t>> waitEdges;
|
||||
std::set<std::pair<size_t, size_t>> visited;
|
||||
std::map<std::pair<size_t, size_t>, size_t> halfEdgeToCycleMap;
|
||||
|
||||
auto isPathValid = [&](const std::vector<size_t> &path) {
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t j = (i + 1) % path.size();
|
||||
auto edge = std::make_pair(path[i], path[j]);
|
||||
if (m_halfEdges.find(edge) != m_halfEdges.end()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::map<size_t, int> oppositeCycleEdgeLengths;
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t j = (i + 1) % path.size();
|
||||
auto oppositeEdge = std::make_pair(path[j], path[i]);
|
||||
auto findOpposite = halfEdgeToCycleMap.find(oppositeEdge);
|
||||
if (findOpposite == halfEdgeToCycleMap.end())
|
||||
continue;
|
||||
oppositeCycleEdgeLengths[findOpposite->second] += m_edgeLengthMap[oppositeEdge];
|
||||
}
|
||||
for (const auto &it: oppositeCycleEdgeLengths) {
|
||||
if (it.first >= m_cycleLengths.size()) {
|
||||
qDebug() << "Find cycle length failed, should not happen";
|
||||
return false;
|
||||
}
|
||||
const auto &fullLength = m_cycleLengths[it.first];
|
||||
if (fullLength <= 0) {
|
||||
qDebug() << "Cycle length invalid, should not happen";
|
||||
return false;
|
||||
}
|
||||
if ((float)it.second / fullLength >= 0.5) {
|
||||
// Half of the edges (measured by sum of length) have been used by opposite face
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!validateCycleByFlatness(path))
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
std::map<std::pair<size_t, size_t>, size_t> edgeIndexMap;
|
||||
for (size_t i = 0; i < m_edges.size(); ++i) {
|
||||
const auto &edge = m_edges[i];
|
||||
edgeIndexMap.insert({edge, i});
|
||||
edgeIndexMap.insert({std::make_pair(edge.second, edge.first), i});
|
||||
}
|
||||
|
||||
waitEdges.push(m_edges[0]);
|
||||
while (!waitEdges.empty()) {
|
||||
auto currentEdge = waitEdges.front();
|
||||
waitEdges.pop();
|
||||
if (visited.find(currentEdge) != visited.end())
|
||||
continue;
|
||||
visited.insert(currentEdge);
|
||||
|
||||
auto edges = m_edges;
|
||||
auto weights = m_edgeLengths;
|
||||
std::vector<size_t> path;
|
||||
removeEdgeFrom(currentEdge, &edges, &weights);
|
||||
if (!shortestPath(m_nodeNum, edges, weights, currentEdge.first, currentEdge.second, &path))
|
||||
continue;
|
||||
|
||||
bool isValid = isPathValid(path);
|
||||
|
||||
if (!isValid)
|
||||
continue;
|
||||
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t j = (i + 1) % path.size();
|
||||
auto edge = std::make_pair(path[i], path[j]);
|
||||
m_halfEdges.insert(edge);
|
||||
halfEdgeToCycleMap.insert({edge, m_cycles.size()});
|
||||
}
|
||||
|
||||
m_cycles.push_back(path);
|
||||
m_cycleLengths.push_back(calculateCycleLength(path));
|
||||
|
||||
// Update weights
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t j = (i + 1) % path.size();
|
||||
auto edge = std::make_pair(path[i], path[j]);
|
||||
auto index = edgeIndexMap[edge];
|
||||
m_edgeLengths[index] += 100000;
|
||||
}
|
||||
|
||||
// Add opposite edges to wait queue
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
size_t j = (i + 1) % path.size();
|
||||
auto oppositeEdge = std::make_pair(path[j], path[i]);
|
||||
if (visited.find(oppositeEdge) != visited.end())
|
||||
continue;
|
||||
waitEdges.push(oppositeEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<std::vector<size_t>> &CycleFinder::getCycles()
|
||||
{
|
||||
return m_cycles;
|
||||
}
|
||||
|
||||
void CycleFinder::removeEdgeFrom(const std::pair<size_t, size_t> &edge,
|
||||
std::vector<std::pair<size_t, size_t>> *edges,
|
||||
std::vector<int> *edgeLengths)
|
||||
{
|
||||
auto oppositeEdge = std::make_pair(edge.second, edge.first);
|
||||
for (auto it = edges->begin(); it != edges->end(); ++it) {
|
||||
if (*it == edge || *it == oppositeEdge) {
|
||||
auto index = it - edges->begin();
|
||||
edgeLengths->erase(edgeLengths->begin() + index);
|
||||
edges->erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
#ifndef DUST3D_CYCLE_FINDER
|
||||
#define DUST3D_CYCLE_FINDER
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <QVector3D>
|
||||
|
||||
class CycleFinder
|
||||
{
|
||||
public:
|
||||
CycleFinder(const std::vector<QVector3D> &nodePositions,
|
||||
const std::vector<std::pair<size_t, size_t>> &edges);
|
||||
void find();
|
||||
const std::vector<std::vector<size_t>> &getCycles();
|
||||
private:
|
||||
size_t m_nodeNum = 0;
|
||||
std::vector<QVector3D> m_nodePositions;
|
||||
std::vector<std::pair<size_t, size_t>> m_edges;
|
||||
std::vector<int> m_edgeLengths;
|
||||
std::map<std::pair<size_t, size_t>, int> m_edgeLengthMap;
|
||||
std::vector<std::vector<size_t>> m_cycles;
|
||||
std::vector<int> m_cycleLengths;
|
||||
std::set<std::pair<size_t, size_t>> m_cycleEdges;
|
||||
std::set<std::pair<size_t, size_t>> m_halfEdges;
|
||||
float m_invalidFlatness = 1.0;
|
||||
void removeEdgeFrom(const std::pair<size_t, size_t> &edge,
|
||||
std::vector<std::pair<size_t, size_t>> *edges,
|
||||
std::vector<int> *edgeLengths);
|
||||
void prepareWeights();
|
||||
bool validateCycleByFlatness(const std::vector<size_t> &cycle);
|
||||
int calculateCycleLength(const std::vector<size_t> &cycle);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -21,8 +21,6 @@
|
|||
#include "meshgenerator.h"
|
||||
|
||||
unsigned long Document::m_maxSnapshot = 1000;
|
||||
const float Component::defaultClothStiffness = 0.5f;
|
||||
const size_t Component::defaultClothIteration = 350;
|
||||
|
||||
Document::Document() :
|
||||
SkeletonDocument(),
|
||||
|
@ -1230,16 +1228,6 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
|
|||
component["smoothSeam"] = QString::number(componentIt.second.smoothSeam);
|
||||
if (componentIt.second.polyCount != PolyCount::Original)
|
||||
component["polyCount"] = PolyCountToString(componentIt.second.polyCount);
|
||||
if (componentIt.second.layer != ComponentLayer::Body)
|
||||
component["layer"] = ComponentLayerToString(componentIt.second.layer);
|
||||
if (componentIt.second.clothStiffnessAdjusted())
|
||||
component["clothStiffness"] = QString::number(componentIt.second.clothStiffness);
|
||||
if (componentIt.second.clothIterationAdjusted())
|
||||
component["clothIteration"] = QString::number(componentIt.second.clothIteration);
|
||||
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());
|
||||
|
@ -1682,19 +1670,6 @@ void Document::addFromSnapshot(const Snapshot &snapshot, enum SnapshotSource sou
|
|||
if (smoothSeamIt != componentKv.second.end())
|
||||
component.setSmoothSeam(smoothSeamIt->second.toFloat());
|
||||
component.polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(componentKv.second, "polyCount").toUtf8().constData());
|
||||
component.layer = ComponentLayerFromString(valueOfKeyInMapOrEmpty(componentKv.second, "layer").toUtf8().constData());
|
||||
auto findClothStiffness = componentKv.second.find("clothStiffness");
|
||||
if (findClothStiffness != componentKv.second.end())
|
||||
component.clothStiffness = findClothStiffness->second.toFloat();
|
||||
auto findClothIteration = componentKv.second.find("clothIteration");
|
||||
if (findClothIteration != componentKv.second.end())
|
||||
component.clothIteration = findClothIteration->second.toUInt();
|
||||
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)];
|
||||
|
@ -2508,76 +2483,6 @@ void Document::setComponentPolyCount(QUuid componentId, PolyCount count)
|
|||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::setComponentLayer(QUuid componentId, ComponentLayer layer)
|
||||
{
|
||||
Component *component = (Component *)findComponent(componentId);
|
||||
if (nullptr == component)
|
||||
return;
|
||||
if (component->layer == layer)
|
||||
return;
|
||||
|
||||
component->layer = layer;
|
||||
component->dirty = true;
|
||||
emit componentLayerChanged(componentId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::setComponentClothStiffness(QUuid componentId, float stiffness)
|
||||
{
|
||||
Component *component = (Component *)findComponent(componentId);
|
||||
if (nullptr == component)
|
||||
return;
|
||||
if (qFuzzyCompare(component->clothStiffness, stiffness))
|
||||
return;
|
||||
|
||||
component->clothStiffness = stiffness;
|
||||
component->dirty = true;
|
||||
emit componentClothStiffnessChanged(componentId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::setComponentClothIteration(QUuid componentId, size_t iteration)
|
||||
{
|
||||
Component *component = (Component *)findComponent(componentId);
|
||||
if (nullptr == component)
|
||||
return;
|
||||
if (component->clothIteration == iteration)
|
||||
return;
|
||||
|
||||
component->clothIteration = iteration;
|
||||
component->dirty = true;
|
||||
emit componentClothIterationChanged(componentId);
|
||||
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);
|
||||
|
|
|
@ -26,8 +26,6 @@
|
|||
#include "preferences.h"
|
||||
#include "paintmode.h"
|
||||
#include "proceduralanimation.h"
|
||||
#include "componentlayer.h"
|
||||
#include "clothforce.h"
|
||||
#include "texturepainter.h"
|
||||
|
||||
class MaterialPreviewsGenerator;
|
||||
|
@ -45,8 +43,6 @@ public:
|
|||
class Component
|
||||
{
|
||||
public:
|
||||
static const float defaultClothStiffness;
|
||||
static const size_t defaultClothIteration;
|
||||
Component()
|
||||
{
|
||||
}
|
||||
|
@ -69,11 +65,6 @@ public:
|
|||
float smoothAll = 0.0;
|
||||
float smoothSeam = 0.0;
|
||||
PolyCount polyCount = PolyCount::Original;
|
||||
ComponentLayer layer = ComponentLayer::Body;
|
||||
float clothStiffness = defaultClothStiffness;
|
||||
ClothForce clothForce = ClothForce::Gravitational;
|
||||
float clothOffset = 0.0f;
|
||||
size_t clothIteration = defaultClothIteration;
|
||||
std::vector<QUuid> childrenIds;
|
||||
QString linkData() const
|
||||
{
|
||||
|
@ -193,22 +184,6 @@ public:
|
|||
{
|
||||
return smoothAllAdjusted() || smoothSeamAdjusted();
|
||||
}
|
||||
bool clothStiffnessAdjusted() const
|
||||
{
|
||||
return fabs(clothStiffness - Component::defaultClothStiffness) >= 0.01;
|
||||
}
|
||||
bool clothIterationAdjusted() const
|
||||
{
|
||||
return clothIteration != defaultClothIteration;
|
||||
}
|
||||
bool clothForceAdjusted() const
|
||||
{
|
||||
return ClothForce::Gravitational != clothForce;
|
||||
}
|
||||
bool clothOffsetAdjusted() const
|
||||
{
|
||||
return fabs(clothOffset - 0.0) >= 0.01;
|
||||
}
|
||||
private:
|
||||
std::set<QUuid> m_childrenIdSet;
|
||||
};
|
||||
|
@ -314,10 +289,6 @@ signals:
|
|||
void componentSmoothSeamChanged(QUuid componentId);
|
||||
void componentPolyCountChanged(QUuid componentId);
|
||||
void componentLayerChanged(QUuid componentId);
|
||||
void componentClothStiffnessChanged(QUuid componentId);
|
||||
void componentClothIterationChanged(QUuid componentId);
|
||||
void componentClothForceChanged(QUuid componentId);
|
||||
void componentClothOffsetChanged(QUuid componentId);
|
||||
void nodeRemoved(QUuid nodeId);
|
||||
void edgeRemoved(QUuid edgeId);
|
||||
void nodeRadiusChanged(QUuid nodeId);
|
||||
|
@ -585,11 +556,6 @@ public slots:
|
|||
void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
|
||||
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
|
||||
void setComponentPolyCount(QUuid componentId, PolyCount count);
|
||||
void setComponentLayer(QUuid componentId, ComponentLayer layer);
|
||||
void setComponentClothStiffness(QUuid componentId, float stiffness);
|
||||
void setComponentClothIteration(QUuid componentId, size_t iteration);
|
||||
void setComponentClothForce(QUuid componentId, ClothForce force);
|
||||
void setComponentClothOffset(QUuid componentId, float offset);
|
||||
void hideOtherComponents(QUuid componentId);
|
||||
void lockOtherComponents(QUuid componentId);
|
||||
void hideAllComponents();
|
||||
|
|
|
@ -987,7 +987,6 @@ DocumentWindow::DocumentWindow() :
|
|||
connect(m_partTreeWidget, &PartTreeWidget::setComponentSmoothAll, m_document, &Document::setComponentSmoothAll);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentSmoothSeam, m_document, &Document::setComponentSmoothSeam);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentPolyCount, m_document, &Document::setComponentPolyCount);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentLayer, m_document, &Document::setComponentLayer);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::moveComponent, m_document, &Document::moveComponent);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::removeComponent, m_document, &Document::removeComponent);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::hideOtherComponents, m_document, &Document::hideOtherComponents);
|
||||
|
@ -1002,10 +1001,6 @@ DocumentWindow::DocumentWindow() :
|
|||
connect(m_partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setPartColorState, m_document, &Document::setPartColorState);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentCombineMode, m_document, &Document::setComponentCombineMode);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentClothStiffness, m_document, &Document::setComponentClothStiffness);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentClothIteration, m_document, &Document::setComponentClothIteration);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentClothForce, m_document, &Document::setComponentClothForce);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setComponentClothOffset, m_document, &Document::setComponentClothOffset);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setPartTarget, m_document, &Document::setPartTarget);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::setPartBase, m_document, &Document::setPartBase);
|
||||
connect(m_partTreeWidget, &PartTreeWidget::hideDescendantComponents, m_document, &Document::hideDescendantComponents);
|
||||
|
|
|
@ -17,11 +17,9 @@
|
|||
#include "triangulatefaces.h"
|
||||
#include "remesher.h"
|
||||
#include "polycount.h"
|
||||
#include "clothsimulator.h"
|
||||
#include "isotropicremesh.h"
|
||||
#include "projectfacestonodes.h"
|
||||
#include "document.h"
|
||||
#include "simulateclothmeshes.h"
|
||||
#include "meshstroketifier.h"
|
||||
#include "fileforever.h"
|
||||
#include "snapshotxml.h"
|
||||
|
@ -915,11 +913,6 @@ CombineMode MeshGenerator::componentCombineMode(const std::map<QString, QString>
|
|||
combineMode = CombineMode::Inversion;
|
||||
if (componentRemeshed(component))
|
||||
combineMode = CombineMode::Uncombined;
|
||||
if (combineMode == CombineMode::Normal) {
|
||||
if (ComponentLayer::Body != ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData())) {
|
||||
combineMode = CombineMode::Uncombined;
|
||||
}
|
||||
}
|
||||
}
|
||||
return combineMode;
|
||||
}
|
||||
|
@ -928,17 +921,9 @@ bool MeshGenerator::componentRemeshed(const std::map<QString, QString> *componen
|
|||
{
|
||||
if (nullptr == component)
|
||||
return false;
|
||||
bool isCloth = false;
|
||||
if (ComponentLayer::Cloth == ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData())) {
|
||||
if (nullptr != polyCountValue)
|
||||
*polyCountValue = PolyCountToValue(PolyCount::VeryHighPoly);
|
||||
isCloth = true;
|
||||
}
|
||||
auto polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(*component, "polyCount").toUtf8().constData());
|
||||
if (nullptr != polyCountValue)
|
||||
*polyCountValue = PolyCountToValue(polyCount);
|
||||
if (isCloth)
|
||||
return true;
|
||||
return polyCount != PolyCount::Original;
|
||||
}
|
||||
|
||||
|
@ -967,47 +952,6 @@ QString MeshGenerator::componentColorName(const std::map<QString, QString> *comp
|
|||
return QString();
|
||||
}
|
||||
|
||||
ComponentLayer MeshGenerator::componentLayer(const std::map<QString, QString> *component)
|
||||
{
|
||||
if (nullptr == component)
|
||||
return ComponentLayer::Body;
|
||||
return ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData());
|
||||
}
|
||||
|
||||
float MeshGenerator::componentClothStiffness(const std::map<QString, QString> *component)
|
||||
{
|
||||
if (nullptr == component)
|
||||
return Component::defaultClothStiffness;
|
||||
auto findClothStiffness = component->find("clothStiffness");
|
||||
if (findClothStiffness == component->end())
|
||||
return Component::defaultClothStiffness;
|
||||
return findClothStiffness->second.toFloat();
|
||||
}
|
||||
|
||||
size_t MeshGenerator::componentClothIteration(const std::map<QString, QString> *component)
|
||||
{
|
||||
if (nullptr == component)
|
||||
return Component::defaultClothIteration;
|
||||
auto findClothIteration = component->find("clothIteration");
|
||||
if (findClothIteration == component->end())
|
||||
return Component::defaultClothIteration;
|
||||
return findClothIteration->second.toUInt();
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -1616,8 +1560,6 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
|
|||
{
|
||||
const auto &component = findComponent(componentIdString);
|
||||
if (CombineMode::Uncombined == componentCombineMode(component)) {
|
||||
if (ComponentLayer::Body != componentLayer(component))
|
||||
return;
|
||||
const auto &componentCache = m_cacheContext->components[componentIdString];
|
||||
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
|
||||
qDebug() << "Uncombined mesh is null";
|
||||
|
@ -1638,90 +1580,6 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
|
|||
}
|
||||
}
|
||||
|
||||
void MeshGenerator::collectClothComponentIdStrings(const QString &componentIdString,
|
||||
std::vector<QString> *componentIdStrings)
|
||||
{
|
||||
const auto &component = findComponent(componentIdString);
|
||||
if (ComponentLayer::Cloth == componentLayer(component)) {
|
||||
const auto &componentCache = m_cacheContext->components[componentIdString];
|
||||
if (nullptr == componentCache.mesh) {
|
||||
return;
|
||||
}
|
||||
componentIdStrings->push_back(componentIdString);
|
||||
return;
|
||||
}
|
||||
for (const auto &childIdString: valueOfKeyInMapOrEmpty(*component, "children").split(",")) {
|
||||
if (childIdString.isEmpty())
|
||||
continue;
|
||||
collectClothComponentIdStrings(childIdString, componentIdStrings);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshGenerator::collectClothComponent(const QString &componentIdString)
|
||||
{
|
||||
if (m_clothCollisionTriangles.empty())
|
||||
return;
|
||||
|
||||
std::vector<QString> componentIdStrings;
|
||||
collectClothComponentIdStrings(componentIdString, &componentIdStrings);
|
||||
|
||||
std::vector<ClothMesh> clothMeshes(componentIdStrings.size());
|
||||
for (size_t i = 0; i < componentIdStrings.size(); ++i) {
|
||||
const auto &componentIdString = componentIdStrings[i];
|
||||
const auto &componentCache = m_cacheContext->components[componentIdString];
|
||||
if (nullptr == componentCache.mesh) {
|
||||
return;
|
||||
}
|
||||
const auto &component = findComponent(componentIdString);
|
||||
auto &clothMesh = clothMeshes[i];
|
||||
componentCache.mesh->fetch(clothMesh.vertices, clothMesh.faces);
|
||||
clothMesh.clothForce = componentClothForce(component);
|
||||
clothMesh.clothOffset = componentClothOffset(component);
|
||||
clothMesh.clothStiffness = componentClothStiffness(component);
|
||||
clothMesh.clothIteration = componentClothIteration(component);
|
||||
clothMesh.objectNodeVertices = &componentCache.objectNodeVertices;
|
||||
//m_object->clothNodes.insert(m_object->clothNodes.end(), componentCache.objectNodes.begin(), componentCache.objectNodes.end());
|
||||
//m_object->nodes.insert(m_object->nodes.end(), componentCache.objectNodes.begin(), componentCache.objectNodes.end());
|
||||
for (const auto &objectNode: componentCache.objectNodes) {
|
||||
auto newNode = objectNode;
|
||||
newNode.layer = ComponentLayer::Cloth;
|
||||
m_object->nodes.push_back(newNode);
|
||||
}
|
||||
m_object->edges.insert(m_object->edges.end(), componentCache.objectEdges.begin(), componentCache.objectEdges.end());
|
||||
}
|
||||
simulateClothMeshes(&clothMeshes,
|
||||
&m_clothCollisionVertices,
|
||||
&m_clothCollisionTriangles);
|
||||
for (auto &clothMesh: clothMeshes) {
|
||||
auto vertexStartIndex = m_object->vertices.size();
|
||||
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
|
||||
for (auto &it: faces) {
|
||||
for (auto &subIt: it)
|
||||
subIt += vertexStartIndex;
|
||||
}
|
||||
};
|
||||
updateVertexIndices(clothMesh.faces);
|
||||
m_object->vertices.insert(m_object->vertices.end(), clothMesh.vertices.begin(), clothMesh.vertices.end());
|
||||
for (const auto &it: clothMesh.faces) {
|
||||
if (4 == it.size()) {
|
||||
m_object->triangles.push_back(std::vector<size_t> {
|
||||
it[0], it[1], it[2]
|
||||
});
|
||||
m_object->triangles.push_back(std::vector<size_t> {
|
||||
it[2], it[3], it[0]
|
||||
});
|
||||
} else if (3 == it.size()) {
|
||||
m_object->triangles.push_back(it);
|
||||
}
|
||||
}
|
||||
m_object->triangleAndQuads.insert(m_object->triangleAndQuads.end(), clothMesh.faces.begin(), clothMesh.faces.end());
|
||||
for (size_t i = 0; i < clothMesh.vertices.size(); ++i) {
|
||||
const auto &source = clothMesh.vertexSources[i];
|
||||
m_nodeVertices.push_back(std::make_pair(clothMesh.vertices[i], source));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshGenerator::generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
|
||||
const std::vector<QVector3D> &triangleNormals,
|
||||
std::vector<std::vector<QVector3D>> *triangleVertexNormals)
|
||||
|
@ -1968,25 +1826,6 @@ void MeshGenerator::generate()
|
|||
collectUncombinedComponent(QUuid().toString());
|
||||
collectIncombinableComponentMeshes(QUuid().toString());
|
||||
|
||||
// Fetch nodes as body nodes before cloth nodes collecting
|
||||
//std::set<std::pair<QUuid, QUuid>> bodyNodeMap;
|
||||
//m_object->bodyNodes.reserve(m_object->nodes.size());
|
||||
//for (const auto &it: m_object->nodes) {
|
||||
// if (it.joined) {
|
||||
// bodyNodeMap.insert({it.partId, it.nodeId});
|
||||
// m_object->bodyNodes.push_back(it);
|
||||
// }
|
||||
//}
|
||||
//m_object->bodyEdges.reserve(m_object->edges.size());
|
||||
//for (const auto &it: m_object->edges) {
|
||||
// if (bodyNodeMap.find(it.first) == bodyNodeMap.end())
|
||||
// continue;
|
||||
// if (bodyNodeMap.find(it.second) == bodyNodeMap.end())
|
||||
// continue;
|
||||
// m_object->bodyEdges.push_back(it);
|
||||
//}
|
||||
|
||||
collectClothComponent(QUuid().toString());
|
||||
collectErroredParts();
|
||||
postprocessObject(m_object);
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
#include "snapshot.h"
|
||||
#include "combinemode.h"
|
||||
#include "model.h"
|
||||
#include "componentlayer.h"
|
||||
#include "clothforce.h"
|
||||
|
||||
class GeneratedPart
|
||||
{
|
||||
|
@ -170,15 +168,7 @@ private:
|
|||
GeneratedComponent &componentCache);
|
||||
MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true);
|
||||
QString componentColorName(const std::map<QString, QString> *component);
|
||||
ComponentLayer componentLayer(const std::map<QString, QString> *component);
|
||||
float componentClothStiffness(const std::map<QString, QString> *component);
|
||||
size_t componentClothIteration(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 collectClothComponentIdStrings(const QString &componentIdString,
|
||||
std::vector<QString> *componentIdStrings);
|
||||
void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate);
|
||||
void remesh(const std::vector<ObjectNode> &inputNodes,
|
||||
const std::vector<std::tuple<QVector3D, float, size_t>> &interpolatedNodes,
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <QVector2D>
|
||||
#include <QRectF>
|
||||
#include "bonemark.h"
|
||||
#include "componentlayer.h"
|
||||
|
||||
struct ObjectNode
|
||||
{
|
||||
|
@ -26,7 +25,6 @@ struct ObjectNode
|
|||
QUuid mirroredByPartId;
|
||||
BoneMark boneMark = BoneMark::None;
|
||||
QVector3D direction;
|
||||
ComponentLayer layer = ComponentLayer::Body;
|
||||
bool joined = true;
|
||||
};
|
||||
|
||||
|
|
|
@ -43,8 +43,6 @@ void saveObjectToXmlStream(const Object *object, QXmlStreamWriter *writer)
|
|||
writer->writeAttribute("mirroredByPartId", node.mirroredByPartId.toString());
|
||||
if (node.boneMark != BoneMark::None)
|
||||
writer->writeAttribute("boneMark", BoneMarkToString(node.boneMark));
|
||||
if (ComponentLayer::Body != node.layer)
|
||||
writer->writeAttribute("layer", ComponentLayerToString(node.layer));
|
||||
if (!node.joined)
|
||||
writer->writeAttribute("joined", "false");
|
||||
writer->writeEndElement();
|
||||
|
@ -250,7 +248,6 @@ void loadObjectFromXmlStream(Object *object, QXmlStreamReader &reader)
|
|||
node.mirrorFromPartId = QUuid(reader.attributes().value("mirrorFromPartId").toString());
|
||||
node.mirroredByPartId = QUuid(reader.attributes().value("mirroredByPartId").toString());
|
||||
node.boneMark = BoneMarkFromString(reader.attributes().value("boneMark").toString().toUtf8().constData());
|
||||
node.layer = ComponentLayerFromString(reader.attributes().value("layer").toString().toUtf8().constData());
|
||||
QString joinedString = reader.attributes().value("joined").toString();
|
||||
if (!joinedString.isEmpty())
|
||||
node.joined = isTrueValueString(joinedString);
|
||||
|
|
|
@ -257,119 +257,6 @@ void PartTreeWidget::mousePressEvent(QMouseEvent *event)
|
|||
handleSingleClick(event->pos());
|
||||
}
|
||||
|
||||
void PartTreeWidget::showClothSettingMenu(const QPoint &pos, const QUuid &componentId)
|
||||
{
|
||||
const Component *component = nullptr;
|
||||
|
||||
if (componentId.isNull())
|
||||
return;
|
||||
|
||||
component = m_document->findComponent(componentId);
|
||||
if (nullptr == component)
|
||||
return;
|
||||
|
||||
QMenu popupMenu;
|
||||
|
||||
QWidget *popup = new QWidget;
|
||||
|
||||
FloatNumberWidget *clothStiffnessWidget = new FloatNumberWidget;
|
||||
clothStiffnessWidget->setItemName(tr("Stiffness"));
|
||||
clothStiffnessWidget->setRange(0.0f, 1.0f);
|
||||
clothStiffnessWidget->setValue(component->clothStiffness);
|
||||
|
||||
connect(clothStiffnessWidget, &FloatNumberWidget::valueChanged, [=](float value) {
|
||||
emit setComponentClothStiffness(componentId, value);
|
||||
emit groupOperationAdded();
|
||||
});
|
||||
|
||||
QPushButton *clothStiffnessEraser = new QPushButton(QChar(fa::eraser));
|
||||
Theme::initAwesomeToolButton(clothStiffnessEraser);
|
||||
|
||||
connect(clothStiffnessEraser, &QPushButton::clicked, [=]() {
|
||||
clothStiffnessWidget->setValue(Component::defaultClothStiffness);
|
||||
emit groupOperationAdded();
|
||||
});
|
||||
|
||||
QHBoxLayout *clothStiffnessLayout = new QHBoxLayout;
|
||||
clothStiffnessLayout->addWidget(clothStiffnessEraser);
|
||||
clothStiffnessLayout->addWidget(clothStiffnessWidget);
|
||||
|
||||
IntNumberWidget *clothIterationWidget = new IntNumberWidget;
|
||||
clothIterationWidget->setItemName(tr("Iteration"));
|
||||
clothIterationWidget->setRange(0, 1000);
|
||||
clothIterationWidget->setValue(component->clothIteration);
|
||||
|
||||
connect(clothIterationWidget, &IntNumberWidget::valueChanged, [=](int value) {
|
||||
//emit setComponentClothIteration(componentId, value);
|
||||
//emit groupOperationAdded();
|
||||
});
|
||||
|
||||
QPushButton *clothIterationEraser = new QPushButton(QChar(fa::eraser));
|
||||
Theme::initAwesomeToolButton(clothIterationEraser);
|
||||
|
||||
connect(clothIterationEraser, &QPushButton::clicked, [=]() {
|
||||
clothIterationWidget->setValue(Component::defaultClothIteration);
|
||||
emit groupOperationAdded();
|
||||
});
|
||||
|
||||
QHBoxLayout *clothIterationLayout = new QHBoxLayout;
|
||||
clothIterationLayout->addWidget(clothIterationEraser);
|
||||
clothIterationLayout->addWidget(clothIterationWidget);
|
||||
|
||||
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);
|
||||
|
||||
connect(clothOffsetEraser, &QPushButton::clicked, [=]() {
|
||||
clothOffsetWidget->setValue(0.0);
|
||||
emit groupOperationAdded();
|
||||
});
|
||||
|
||||
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(clothIterationLayout);
|
||||
mainLayout->addLayout(clothOffsetLayout);
|
||||
mainLayout->addLayout(clothForceLayout);
|
||||
|
||||
popup->setLayout(mainLayout);
|
||||
|
||||
QWidgetAction action(this);
|
||||
action.setDefaultWidget(popup);
|
||||
|
||||
popupMenu.addAction(&action);
|
||||
|
||||
popupMenu.exec(mapToGlobal(pos));
|
||||
}
|
||||
|
||||
std::vector<QUuid> PartTreeWidget::collectSelectedComponentIds(const QPoint &pos)
|
||||
{
|
||||
std::set<QUuid> unorderedComponentIds = m_selectedComponentIds;
|
||||
|
@ -486,33 +373,7 @@ void PartTreeWidget::showContextMenu(const QPoint &pos, bool shorted)
|
|||
}
|
||||
}
|
||||
|
||||
QHBoxLayout *componentLayerLayout = nullptr;
|
||||
|
||||
if (nullptr != component) {
|
||||
if (nullptr == part || part->hasLayerFunction()) {
|
||||
QPushButton *clothSettingButton = new QPushButton();
|
||||
connect(clothSettingButton, &QPushButton::clicked, this, [=]() {
|
||||
showClothSettingMenu(mapFromGlobal(QCursor::pos()), component->id);
|
||||
});
|
||||
clothSettingButton->setIcon(Theme::awesome()->icon(fa::gear));
|
||||
if (ComponentLayer::Cloth != component->layer)
|
||||
clothSettingButton->hide();
|
||||
QComboBox *componentLayerSelectBox = new QComboBox;
|
||||
for (size_t i = 0; i < (size_t)ComponentLayer::Count; ++i) {
|
||||
ComponentLayer layer = (ComponentLayer)i;
|
||||
componentLayerSelectBox->addItem(ComponentLayerToDispName(layer));
|
||||
}
|
||||
componentLayerSelectBox->setCurrentIndex((int)component->layer);
|
||||
connect(componentLayerSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
|
||||
clothSettingButton->setVisible(ComponentLayer::Cloth == (ComponentLayer)index);
|
||||
emit setComponentLayer(component->id, (ComponentLayer)index);
|
||||
emit groupOperationAdded();
|
||||
});
|
||||
componentLayerLayout = new QHBoxLayout;
|
||||
componentLayerLayout->addWidget(componentLayerSelectBox);
|
||||
componentLayerLayout->addWidget(clothSettingButton);
|
||||
componentLayerLayout->setStretch(0, 1);
|
||||
}
|
||||
|
||||
if (nullptr == part || part->hasCombineModeFunction()) {
|
||||
combineModeSelectBox = new QComboBox;
|
||||
|
@ -669,8 +530,6 @@ void PartTreeWidget::showContextMenu(const QPoint &pos, bool shorted)
|
|||
componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox);
|
||||
if (nullptr != combineModeSelectBox)
|
||||
componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox);
|
||||
if (nullptr != componentLayerLayout)
|
||||
componentSettingsLayout->addRow(tr("Layer"), componentLayerLayout);
|
||||
|
||||
QVBoxLayout *newLayout = new QVBoxLayout;
|
||||
newLayout->addLayout(layout);
|
||||
|
|
|
@ -24,7 +24,6 @@ signals:
|
|||
void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
|
||||
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
|
||||
void setComponentPolyCount(QUuid componentId, PolyCount count);
|
||||
void setComponentLayer(QUuid componentId, ComponentLayer layer);
|
||||
void setPartTarget(QUuid partId, PartTarget target);
|
||||
void setPartBase(QUuid partId, PartBase base);
|
||||
void moveComponent(QUuid componentId, QUuid toParentId);
|
||||
|
@ -42,10 +41,6 @@ signals:
|
|||
void setPartVisibleState(QUuid partId, bool visible);
|
||||
void setPartColorState(QUuid partId, bool hasColor, QColor color);
|
||||
void setComponentCombineMode(QUuid componentId, CombineMode combineMode);
|
||||
void setComponentClothStiffness(QUuid componentId, float clothStiffness);
|
||||
void setComponentClothIteration(QUuid componentId, size_t iteration);
|
||||
void setComponentClothForce(QUuid componentId, ClothForce force);
|
||||
void setComponentClothOffset(QUuid componentId, float offset);
|
||||
void hideDescendantComponents(QUuid componentId);
|
||||
void showDescendantComponents(QUuid componentId);
|
||||
void lockDescendantComponents(QUuid componentId);
|
||||
|
@ -91,7 +86,6 @@ public slots:
|
|||
void groupCollapsed(QTreeWidgetItem *item);
|
||||
void removeAllContent();
|
||||
void showContextMenu(const QPoint &pos, bool shorted=false);
|
||||
void showClothSettingMenu(const QPoint &pos, const QUuid &componentId);
|
||||
protected:
|
||||
QSize sizeHint() const override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include "materiallistwidget.h"
|
||||
#include "infolabel.h"
|
||||
#include "cutface.h"
|
||||
#include "cutdocument.h"
|
||||
#include "skeletongraphicswidget.h"
|
||||
#include "shortcuts.h"
|
||||
#include "graphicscontainerwidget.h"
|
||||
|
|
|
@ -149,8 +149,6 @@ void RigGenerator::buildNeighborMap()
|
|||
std::map<std::pair<QUuid, QUuid>, size_t> nodeIdToIndexMap;
|
||||
for (size_t i = 0; i < m_object->nodes.size(); ++i) {
|
||||
const auto &node = m_object->nodes[i];
|
||||
if (ComponentLayer::Body != node.layer)
|
||||
continue;
|
||||
nodeIdToIndexMap.insert({{node.partId, node.nodeId}, i});
|
||||
m_neighborMap.insert({i, {}});
|
||||
}
|
||||
|
@ -220,8 +218,6 @@ void RigGenerator::buildBoneNodeChain()
|
|||
size_t middleStartNodeIndex = m_object->nodes.size();
|
||||
for (size_t nodeIndex = 0; nodeIndex < m_object->nodes.size(); ++nodeIndex) {
|
||||
const auto &node = m_object->nodes[nodeIndex];
|
||||
if (ComponentLayer::Body != node.layer)
|
||||
continue;
|
||||
if (!BoneMarkIsBranchNode(node.boneMark))
|
||||
continue;
|
||||
m_branchNodesMapByMark[(int)node.boneMark].push_back(nodeIndex);
|
||||
|
@ -655,21 +651,15 @@ void RigGenerator::computeSkinWeights()
|
|||
std::map<std::pair<QUuid, QUuid>, size_t> nodeIdToIndexMap;
|
||||
for (size_t nodeIndex = 0; nodeIndex < m_object->nodes.size(); ++nodeIndex) {
|
||||
const auto &node = m_object->nodes[nodeIndex];
|
||||
if (ComponentLayer::Body != node.layer)
|
||||
continue;
|
||||
nodeIdToIndexMap[{node.partId, node.nodeId}] = nodeIndex;
|
||||
}
|
||||
if (!nodeIdToIndexMap.empty()) {
|
||||
for (size_t nonBodyNodeIndex = 0; nonBodyNodeIndex < m_object->nodes.size(); ++nonBodyNodeIndex) {
|
||||
const auto &nonBodyNode = m_object->nodes[nonBodyNodeIndex];
|
||||
if (ComponentLayer::Body == nonBodyNode.layer)
|
||||
continue;
|
||||
std::vector<std::pair<size_t, float>> distance2s;
|
||||
distance2s.reserve(m_object->nodes.size());
|
||||
for (size_t nodeIndex = 0; nodeIndex < m_object->nodes.size(); ++nodeIndex) {
|
||||
const auto &node = m_object->nodes[nodeIndex];
|
||||
if (ComponentLayer::Body != node.layer)
|
||||
continue;
|
||||
distance2s.push_back(std::make_pair(nodeIndex,
|
||||
(nonBodyNode.origin - node.origin).lengthSquared()));
|
||||
}
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
#include <boost/config.hpp>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <boost/graph/graph_traits.hpp>
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/dijkstra_shortest_paths.hpp>
|
||||
#include <QDebug>
|
||||
#include "shortestpath.h"
|
||||
|
||||
using namespace boost;
|
||||
|
||||
bool shortestPath(size_t nodeNum,
|
||||
const std::vector<std::pair<size_t, size_t>> &edges,
|
||||
const std::vector<int> &weights,
|
||||
size_t start,
|
||||
size_t stop,
|
||||
std::vector<size_t> *path)
|
||||
{
|
||||
typedef adjacency_list < listS, vecS, undirectedS,
|
||||
no_property, property < edge_weight_t, int > > graph_t;
|
||||
typedef graph_traits < graph_t >::vertex_descriptor vertex_descriptor;
|
||||
typedef graph_traits < graph_t >::edge_descriptor edge_descriptor;
|
||||
typedef std::pair<size_t, size_t> Edge;
|
||||
|
||||
size_t edgeNum = edges.size();
|
||||
#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300
|
||||
graph_t g(nodeNum);
|
||||
property_map<graph_t, edge_weight_t>::type weightmap = get(edge_weight, g);
|
||||
for (std::size_t j = 0; j < edgeNum; ++j) {
|
||||
edge_descriptor e; bool inserted;
|
||||
tie(e, inserted) = add_edge(edges[j].first, edges[j].second, g);
|
||||
weightmap[e] = weights[j];
|
||||
}
|
||||
#else
|
||||
graph_t g(edges.data(), edges.data() + edgeNum, weights.data(), nodeNum);
|
||||
property_map<graph_t, edge_weight_t>::type weightmap = get(edge_weight, g);
|
||||
#endif
|
||||
|
||||
std::vector<vertex_descriptor> p(num_vertices(g));
|
||||
std::vector<size_t> d(num_vertices(g));
|
||||
vertex_descriptor s = vertex(start, g);
|
||||
|
||||
#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300
|
||||
// VC++ has trouble with the named parameters mechanism
|
||||
property_map<graph_t, vertex_index_t>::type indexmap = get(vertex_index, g);
|
||||
dijkstra_shortest_paths(g, s, &p[0], &d[0], weightmap, indexmap,
|
||||
std::less<size_t>(), closed_plus<size_t>(),
|
||||
(std::numeric_limits<size_t>::max)(), 0,
|
||||
default_dijkstra_visitor());
|
||||
#else
|
||||
dijkstra_shortest_paths(g, s, predecessor_map(&p[0]).distance_map(&d[0]));
|
||||
#endif
|
||||
|
||||
auto current = stop;
|
||||
while (current != start) {
|
||||
path->push_back(current);
|
||||
size_t next = p[current];
|
||||
if (next == current)
|
||||
return false;
|
||||
current = next;
|
||||
}
|
||||
path->push_back(current);
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#ifndef DUST3D_SHORTEST_PATH
|
||||
#define DUST3D_SHORTEST_PATH
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
/*
|
||||
|
||||
Example code:
|
||||
|
||||
std::vector<QString> nodeNames = {
|
||||
"A", //0
|
||||
"B", //1
|
||||
"C", //2
|
||||
"D", //3
|
||||
"E", //4
|
||||
"F" //5
|
||||
};
|
||||
std::vector<std::pair<int, int>> edges = {
|
||||
{1,0},
|
||||
{1,3},
|
||||
{1,4},
|
||||
{3,4},
|
||||
{3,5},
|
||||
{4,5},
|
||||
{5,2},
|
||||
{0,2}
|
||||
};
|
||||
std::vector<int> weights(edges.size(), 1);
|
||||
weights[7] = 10;
|
||||
weights[4] = 10;
|
||||
std::vector<int> path;
|
||||
shortestPath(nodeNames.size(), edges, weights, 0, 5, &path);
|
||||
for (const auto &it: path) {
|
||||
qDebug() << nodeNames[it];
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
bool shortestPath(size_t nodeNum,
|
||||
const std::vector<std::pair<size_t, size_t>> &edges,
|
||||
const std::vector<int> &weights,
|
||||
size_t start,
|
||||
size_t stop,
|
||||
std::vector<size_t> *path);
|
||||
|
||||
#endif
|
|
@ -1,95 +0,0 @@
|
|||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/blocked_range.h>
|
||||
#include "simulateclothmeshes.h"
|
||||
#include "positionkey.h"
|
||||
#include "util.h"
|
||||
#include "clothsimulator.h"
|
||||
|
||||
class ClothMeshesSimulator
|
||||
{
|
||||
public:
|
||||
ClothMeshesSimulator(std::vector<ClothMesh> *clothMeshes,
|
||||
const std::vector<QVector3D> *clothCollisionVertices,
|
||||
const std::vector<std::vector<size_t>> *clothCollisionTriangles) :
|
||||
m_clothMeshes(clothMeshes),
|
||||
m_clothCollisionVertices(clothCollisionVertices),
|
||||
m_clothCollisionTriangles(clothCollisionTriangles)
|
||||
{
|
||||
}
|
||||
void simulate(ClothMesh *clothMesh) const
|
||||
{
|
||||
const auto &filteredClothFaces = clothMesh->faces;
|
||||
std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap;
|
||||
std::pair<QUuid, QUuid> defaultSource;
|
||||
for (const auto &it: *clothMesh->objectNodeVertices) {
|
||||
if (!it.second.first.isNull())
|
||||
defaultSource.first = it.second.first;
|
||||
positionMap.insert({PositionKey(it.first), it.second});
|
||||
}
|
||||
clothMesh->vertexSources.resize(clothMesh->vertices.size(), defaultSource);
|
||||
for (size_t i = 0; i < clothMesh->vertices.size(); ++i) {
|
||||
auto findSource = positionMap.find(PositionKey(clothMesh->vertices[i]));
|
||||
if (findSource == positionMap.end())
|
||||
continue;
|
||||
clothMesh->vertexSources[i] = findSource->second;
|
||||
}
|
||||
|
||||
std::vector<QVector3D> &filteredClothVertices = clothMesh->vertices;
|
||||
std::vector<QVector3D> externalForces;
|
||||
std::vector<QVector3D> postProcessDirections;
|
||||
const auto &clothForce = clothMesh->clothForce;
|
||||
float clothOffset = 0.01f + (clothMesh->clothOffset * 0.05f);
|
||||
if (ClothForce::Centripetal == clothForce) {
|
||||
externalForces.resize(filteredClothVertices.size());
|
||||
postProcessDirections.resize(filteredClothVertices.size());
|
||||
for (size_t i = 0; i < filteredClothFaces.size(); ++i) {
|
||||
const auto &face = filteredClothFaces[i];
|
||||
auto faceForceDirection = -polygonNormal(filteredClothVertices, face);
|
||||
for (const auto &vertex: face)
|
||||
externalForces[vertex] += faceForceDirection;
|
||||
}
|
||||
for (size_t i = 0; i < externalForces.size(); ++i) {
|
||||
auto &it = externalForces[i];
|
||||
it.normalize();
|
||||
postProcessDirections[i] = it * clothOffset;
|
||||
it = (it + QVector3D(0.0f, -1.0f, 0.0f)).normalized();
|
||||
}
|
||||
} else {
|
||||
postProcessDirections.resize(filteredClothVertices.size(), QVector3D(0.0f, -1.0f, 0.0f) * clothOffset);
|
||||
externalForces.resize(filteredClothVertices.size(), QVector3D(0.0f, -1.0f, 0.0f));
|
||||
}
|
||||
ClothSimulator clothSimulator(filteredClothVertices,
|
||||
filteredClothFaces,
|
||||
*m_clothCollisionVertices,
|
||||
*m_clothCollisionTriangles,
|
||||
externalForces);
|
||||
clothSimulator.setStiffness(clothMesh->clothStiffness);
|
||||
clothSimulator.create();
|
||||
for (size_t i = 0; i < clothMesh->clothIteration; ++i)
|
||||
clothSimulator.step();
|
||||
clothSimulator.getCurrentVertices(&filteredClothVertices);
|
||||
for (size_t i = 0; i < filteredClothVertices.size(); ++i) {
|
||||
filteredClothVertices[i] -= postProcessDirections[i];
|
||||
}
|
||||
}
|
||||
void operator()(const tbb::blocked_range<size_t> &range) const
|
||||
{
|
||||
for (size_t i = range.begin(); i != range.end(); ++i) {
|
||||
simulate(&(*m_clothMeshes)[i]);
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::vector<ClothMesh> *m_clothMeshes = nullptr;
|
||||
const std::vector<QVector3D> *m_clothCollisionVertices = nullptr;
|
||||
const std::vector<std::vector<size_t>> *m_clothCollisionTriangles = nullptr;
|
||||
};
|
||||
|
||||
void simulateClothMeshes(std::vector<ClothMesh> *clothMeshes,
|
||||
const std::vector<QVector3D> *clothCollisionVertices,
|
||||
const std::vector<std::vector<size_t>> *clothCollisionTriangles)
|
||||
{
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, clothMeshes->size()),
|
||||
ClothMeshesSimulator(clothMeshes,
|
||||
clothCollisionVertices,
|
||||
clothCollisionTriangles));
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
#ifndef DUST3D_SIMULATE_CLOTH_MESHES_H
|
||||
#define DUST3D_SIMULATE_CLOTH_MESHES_H
|
||||
#include <QVector3D>
|
||||
#include <vector>
|
||||
#include <QUuid>
|
||||
#include "clothforce.h"
|
||||
|
||||
struct ClothMesh
|
||||
{
|
||||
std::vector<QVector3D> vertices;
|
||||
std::vector<std::vector<size_t>> faces;
|
||||
std::vector<std::pair<QUuid, QUuid>> vertexSources;
|
||||
const std::vector<std::pair<QVector3D, std::pair<QUuid, QUuid>>> *objectNodeVertices;
|
||||
ClothForce clothForce;
|
||||
float clothOffset;
|
||||
float clothStiffness;
|
||||
size_t clothIteration;
|
||||
};
|
||||
|
||||
void simulateClothMeshes(std::vector<ClothMesh> *clothMeshes,
|
||||
const std::vector<QVector3D> *clothCollisionVertices,
|
||||
const std::vector<std::vector<size_t>> *clothCollisionTriangles);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue