Add cloth simulation

Integrate FastMassSpring, implemented by Samer Itani(@sam007961),
Original paper: "Fast Simulation of Mass-Spring Systems" by Liu, T., Bargteil, A. W., Obrien, J. F., & Kavan, L.
master
Jeremy Hu 2020-01-08 23:46:56 +09:30
parent 56f0743845
commit 579fa24e1c
36 changed files with 2856 additions and 0 deletions

View File

@ -1272,3 +1272,28 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
other computer software, distribute, and sublicense such enhancements or other computer software, distribute, and sublicense such enhancements or
derivative works thereof, in binary and source code form. derivative works thereof, in binary and source code form.
</pre> </pre>
<h1>FastMassSpring</h1>
<pre>
MIT License
Copyright (c) 2019 Samer Itani
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>

View File

@ -482,10 +482,20 @@ HEADERS += src/contourtopartconverter.h
SOURCES += src/remesher.cpp SOURCES += src/remesher.cpp
HEADERS += src/remesher.h HEADERS += src/remesher.h
SOURCES += src/clothsimulator.cpp
HEADERS += src/clothsimulator.h
SOURCES += src/componentlayer.cpp
HEADERS += src/componentlayer.h
SOURCES += src/main.cpp SOURCES += src/main.cpp
HEADERS += src/version.h HEADERS += src/version.h
INCLUDEPATH += thirdparty/FastMassSpring/ClothApp
SOURCES += thirdparty/FastMassSpring/ClothApp/MassSpringSolver.cpp
HEADERS += thirdparty/FastMassSpring/ClothApp/MassSpringSolver.h
INCLUDEPATH += thirdparty/instant-meshes INCLUDEPATH += thirdparty/instant-meshes
INCLUDEPATH += thirdparty/instant-meshes/instant-meshes-dust3d/src INCLUDEPATH += thirdparty/instant-meshes/instant-meshes-dust3d/src
INCLUDEPATH += thirdparty/instant-meshes/instant-meshes-dust3d/ext/tbb/include INCLUDEPATH += thirdparty/instant-meshes/instant-meshes-dust3d/ext/tbb/include

View File

@ -748,6 +748,10 @@ Tips:
<source>Poly</source> <source>Poly</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Layer</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>PartWidget</name> <name>PartWidget</name>
@ -1070,6 +1074,14 @@ Tips:
<source>Extreme High Poly</source> <source>Extreme High Poly</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Body</source>
<translation></translation>
</message>
<message>
<source>Cloth</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>RigWidget</name> <name>RigWidget</name>

240
src/clothsimulator.cpp Normal file
View File

@ -0,0 +1,240 @@
#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 w = 2.0f; // width | 2.0f
static const float h = 0.008f; // time step, smaller for better results | 0.008f = 0.016f/2
static const float r = w / (n - 1) * 1.05f; // spring rest legnth
static const float k = 1.0f; // spring stiffness | 1.0f;
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();
}
}
}
};
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) :
m_vertices(vertices),
m_faces(faces),
m_collisionVertices(collisionVertices),
m_collisionTriangles(collisionTriangles)
{
}
ClothSimulator::~ClothSimulator()
{
delete m_massSpringSystem;
delete m_massSpringSolver;
delete m_rootNode;
delete m_deformationNode;
delete m_meshCollisionNode;
}
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;
mass_spring_system::VectorXf masses(SystemParam::m * 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();
stiffnesses[i] = SystemParam::k;
}
mass_spring_system::VectorXf fext = Eigen::Vector3f(0, -SystemParam::g, 0).replicate(m_clothPointSources.size(), 1);
m_massSpringSystem = new mass_spring_system(m_clothPointSources.size(), m_clothSprings.size(), SystemParam::h, springList, restLengths,
stiffnesses, masses, fext, SystemParam::a);
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_rootNode->addChild(m_meshCollisionNode);
}

41
src/clothsimulator.h Normal file
View File

@ -0,0 +1,41 @@
#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 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);
~ClothSimulator();
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<float> m_clothPointBuffer;
std::vector<size_t> m_clothPointSources;
std::vector<std::pair<size_t, size_t>> m_clothSprings;
mass_spring_system *m_massSpringSystem = nullptr;
MassSpringSolver *m_massSpringSolver = nullptr;
CgRootNode *m_rootNode = nullptr;
CgSpringDeformationNode *m_deformationNode = nullptr;
CgMeshCollisionNode *m_meshCollisionNode = nullptr;
void convertMeshToCloth();
};
#endif

6
src/componentlayer.cpp Normal file
View File

@ -0,0 +1,6 @@
#include <QObject>
#include "componentlayer.h"
IMPL_ComponentLayerToString
IMPL_ComponentLayerFromString
IMPL_ComponentLayerToDispName

49
src/componentlayer.h Normal file
View File

@ -0,0 +1,49 @@
#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

View File

@ -1201,6 +1201,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
component["smoothSeam"] = QString::number(componentIt.second.smoothSeam); component["smoothSeam"] = QString::number(componentIt.second.smoothSeam);
if (componentIt.second.polyCount != PolyCount::Original) if (componentIt.second.polyCount != PolyCount::Original)
component["polyCount"] = PolyCountToString(componentIt.second.polyCount); component["polyCount"] = PolyCountToString(componentIt.second.polyCount);
if (componentIt.second.layer != ComponentLayer::Body)
component["layer"] = ComponentLayerToString(componentIt.second.layer);
QStringList childIdList; QStringList childIdList;
for (const auto &childId: componentIt.second.childrenIds) { for (const auto &childId: componentIt.second.childrenIds) {
childIdList.append(childId.toString()); childIdList.append(childId.toString());
@ -1708,6 +1710,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
if (smoothSeamIt != componentKv.second.end()) if (smoothSeamIt != componentKv.second.end())
component.setSmoothSeam(smoothSeamIt->second.toFloat()); component.setSmoothSeam(smoothSeamIt->second.toFloat());
component.polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(componentKv.second, "polyCount").toUtf8().constData()); component.polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(componentKv.second, "polyCount").toUtf8().constData());
component.layer = ComponentLayerFromString(valueOfKeyInMapOrEmpty(componentKv.second, "layer").toUtf8().constData());
//qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name; //qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name;
if ("partId" == linkDataType) { if ("partId" == linkDataType) {
QUuid partId = oldNewIdMap[QUuid(linkData)]; QUuid partId = oldNewIdMap[QUuid(linkData)];
@ -2497,6 +2500,20 @@ void Document::setComponentPolyCount(QUuid componentId, PolyCount count)
emit skeletonChanged(); 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::createNewComponentAndMoveThisIn(QUuid componentId) void Document::createNewComponentAndMoveThisIn(QUuid componentId)
{ {
auto component = componentMap.find(componentId); auto component = componentMap.find(componentId);

View File

@ -30,6 +30,7 @@
#include "preferences.h" #include "preferences.h"
#include "paintmode.h" #include "paintmode.h"
#include "proceduralanimation.h" #include "proceduralanimation.h"
#include "componentlayer.h"
class MaterialPreviewsGenerator; class MaterialPreviewsGenerator;
class MotionsGenerator; class MotionsGenerator;
@ -67,6 +68,7 @@ public:
float smoothAll = 0.0; float smoothAll = 0.0;
float smoothSeam = 0.0; float smoothSeam = 0.0;
PolyCount polyCount = PolyCount::Original; PolyCount polyCount = PolyCount::Original;
ComponentLayer layer = ComponentLayer::Body;
std::vector<QUuid> childrenIds; std::vector<QUuid> childrenIds;
QString linkData() const QString linkData() const
{ {
@ -396,6 +398,7 @@ signals:
void componentSmoothAllChanged(QUuid componentId); void componentSmoothAllChanged(QUuid componentId);
void componentSmoothSeamChanged(QUuid componentId); void componentSmoothSeamChanged(QUuid componentId);
void componentPolyCountChanged(QUuid componentId); void componentPolyCountChanged(QUuid componentId);
void componentLayerChanged(QUuid componentId);
void nodeRemoved(QUuid nodeId); void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId); void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId); void nodeRadiusChanged(QUuid nodeId);
@ -642,6 +645,7 @@ public slots:
void setComponentSmoothAll(QUuid componentId, float toSmoothAll); void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam); void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
void setComponentPolyCount(QUuid componentId, PolyCount count); void setComponentPolyCount(QUuid componentId, PolyCount count);
void setComponentLayer(QUuid componentId, ComponentLayer layer);
void hideOtherComponents(QUuid componentId); void hideOtherComponents(QUuid componentId);
void lockOtherComponents(QUuid componentId); void lockOtherComponents(QUuid componentId);
void hideAllComponents(); void hideAllComponents();

View File

@ -1014,6 +1014,7 @@ DocumentWindow::DocumentWindow() :
connect(partTreeWidget, &PartTreeWidget::setComponentSmoothAll, m_document, &Document::setComponentSmoothAll); connect(partTreeWidget, &PartTreeWidget::setComponentSmoothAll, m_document, &Document::setComponentSmoothAll);
connect(partTreeWidget, &PartTreeWidget::setComponentSmoothSeam, m_document, &Document::setComponentSmoothSeam); connect(partTreeWidget, &PartTreeWidget::setComponentSmoothSeam, m_document, &Document::setComponentSmoothSeam);
connect(partTreeWidget, &PartTreeWidget::setComponentPolyCount, m_document, &Document::setComponentPolyCount); connect(partTreeWidget, &PartTreeWidget::setComponentPolyCount, m_document, &Document::setComponentPolyCount);
connect(partTreeWidget, &PartTreeWidget::setComponentLayer, m_document, &Document::setComponentLayer);
connect(partTreeWidget, &PartTreeWidget::moveComponent, m_document, &Document::moveComponent); connect(partTreeWidget, &PartTreeWidget::moveComponent, m_document, &Document::moveComponent);
connect(partTreeWidget, &PartTreeWidget::removeComponent, m_document, &Document::removeComponent); connect(partTreeWidget, &PartTreeWidget::removeComponent, m_document, &Document::removeComponent);
connect(partTreeWidget, &PartTreeWidget::hideOtherComponents, m_document, &Document::hideOtherComponents); connect(partTreeWidget, &PartTreeWidget::hideOtherComponents, m_document, &Document::hideOtherComponents);

View File

@ -18,6 +18,7 @@
#include "triangulatefaces.h" #include "triangulatefaces.h"
#include "remesher.h" #include "remesher.h"
#include "polycount.h" #include "polycount.h"
#include "clothsimulator.h"
MeshGenerator::MeshGenerator(Snapshot *snapshot) : MeshGenerator::MeshGenerator(Snapshot *snapshot) :
m_snapshot(snapshot) m_snapshot(snapshot)
@ -840,6 +841,11 @@ CombineMode MeshGenerator::componentCombineMode(const std::map<QString, QString>
combineMode = CombineMode::Inversion; combineMode = CombineMode::Inversion;
if (componentRemeshed(component)) if (componentRemeshed(component))
combineMode = CombineMode::Uncombined; combineMode = CombineMode::Uncombined;
if (combineMode == CombineMode::Normal) {
if (ComponentLayer::Body != ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData())) {
combineMode = CombineMode::Uncombined;
}
}
} }
return combineMode; return combineMode;
} }
@ -879,6 +885,13 @@ QString MeshGenerator::componentColorName(const std::map<QString, QString> *comp
return QString(); return QString();
} }
ComponentLayer MeshGenerator::componentLayer(const std::map<QString, QString> *component)
{
if (nullptr == component)
return ComponentLayer::Body;
return ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData());
}
MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode) MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &componentIdString, CombineMode *combineMode)
{ {
MeshCombiner::Mesh *mesh = nullptr; MeshCombiner::Mesh *mesh = nullptr;
@ -1035,6 +1048,16 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
mesh = nullptr; mesh = nullptr;
} }
if (componentId.isNull()) {
// Prepare cloth collision shap
if (nullptr != mesh && !mesh->isNull()) {
mesh->fetch(m_clothCollisionVertices, m_clothCollisionTriangles);
} else {
// TODO: when no body is valid, may add ground plane as collision shape
// ... ...
}
}
if (nullptr != mesh) { if (nullptr != mesh) {
float polyCountValue = 1.0f; float polyCountValue = 1.0f;
bool remeshed = componentId.isNull() ? componentRemeshed(&m_snapshot->canvas, &polyCountValue) : componentRemeshed(component, &polyCountValue); bool remeshed = componentId.isNull() ? componentRemeshed(&m_snapshot->canvas, &polyCountValue) : componentRemeshed(component, &polyCountValue);
@ -1386,6 +1409,7 @@ void MeshGenerator::generate()
// Recursively check uncombined components // Recursively check uncombined components
collectUncombinedComponent(QUuid().toString()); collectUncombinedComponent(QUuid().toString());
collectClothComponent(QUuid().toString());
// Collect errored parts // Collect errored parts
for (const auto &it: m_cacheContext->parts) { for (const auto &it: m_cacheContext->parts) {
@ -1514,6 +1538,8 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
{ {
const auto &component = findComponent(componentIdString); const auto &component = findComponent(componentIdString);
if (CombineMode::Uncombined == componentCombineMode(component)) { if (CombineMode::Uncombined == componentCombineMode(component)) {
if (ComponentLayer::Body != componentLayer(component))
return;
const auto &componentCache = m_cacheContext->components[componentIdString]; const auto &componentCache = m_cacheContext->components[componentIdString];
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) { if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
qDebug() << "Uncombined mesh is null"; qDebug() << "Uncombined mesh is null";
@ -1553,6 +1579,71 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
} }
} }
void MeshGenerator::collectClothComponent(const QString &componentIdString)
{
const auto &component = findComponent(componentIdString);
if (ComponentLayer::Cloth == componentLayer(component)) {
const auto &componentCache = m_cacheContext->components[componentIdString];
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
return;
}
if (m_clothCollisionTriangles.empty())
return;
std::vector<QVector3D> uncombinedVertices;
std::vector<std::vector<size_t>> uncombinedFaces;
componentCache.mesh->fetch(uncombinedVertices, uncombinedFaces);
std::map<PositionKey, std::pair<QUuid, QUuid>> positionMap;
for (const auto &it: componentCache.outcomeNodeVertices) {
positionMap.insert({PositionKey(it.first), it.second});
}
std::vector<std::pair<QUuid, QUuid>> uncombinedVertexSources(uncombinedVertices.size());
for (size_t i = 0; i < uncombinedVertices.size(); ++i) {
auto findSource = positionMap.find(PositionKey(uncombinedVertices[i]));
if (findSource == positionMap.end())
continue;
uncombinedVertexSources[i] = findSource->second;
}
ClothSimulator clothSimulator(uncombinedVertices,
uncombinedFaces,
m_clothCollisionVertices,
m_clothCollisionTriangles);
clothSimulator.create();
for (size_t i = 0; i < 30; ++i)
clothSimulator.step();
clothSimulator.getCurrentVertices(&uncombinedVertices);
auto vertexStartIndex = m_outcome->vertices.size();
auto updateVertexIndices = [=](std::vector<std::vector<size_t>> &faces) {
for (auto &it: faces) {
for (auto &subIt: it)
subIt += vertexStartIndex;
}
};
updateVertexIndices(uncombinedFaces);
m_outcome->vertices.insert(m_outcome->vertices.end(), uncombinedVertices.begin(), uncombinedVertices.end());
m_outcome->triangles.insert(m_outcome->triangles.end(), uncombinedFaces.begin(), uncombinedFaces.end());
m_outcome->triangleAndQuads.insert(m_outcome->triangleAndQuads.end(), uncombinedFaces.begin(), uncombinedFaces.end());
m_outcome->nodes.insert(m_outcome->nodes.end(), componentCache.outcomeNodes.begin(), componentCache.outcomeNodes.end());
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));
}
return;
}
for (const auto &childIdString: valueOfKeyInMapOrEmpty(*component, "children").split(",")) {
if (childIdString.isEmpty())
continue;
collectClothComponent(childIdString);
}
}
void MeshGenerator::generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles, void MeshGenerator::generateSmoothTriangleVertexNormals(const std::vector<QVector3D> &vertices, const std::vector<std::vector<size_t>> &triangles,
const std::vector<QVector3D> &triangleNormals, const std::vector<QVector3D> &triangleNormals,
std::vector<std::vector<QVector3D>> *triangleVertexNormals) std::vector<std::vector<QVector3D>> *triangleVertexNormals)

View File

@ -11,6 +11,7 @@
#include "snapshot.h" #include "snapshot.h"
#include "combinemode.h" #include "combinemode.h"
#include "meshloader.h" #include "meshloader.h"
#include "componentlayer.h"
class GeneratedPart class GeneratedPart
{ {
@ -100,6 +101,8 @@ private:
std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *m_cutFaceTransforms = nullptr; std::map<QUuid, StrokeMeshBuilder::CutFaceTransform> *m_cutFaceTransforms = nullptr;
std::map<QUuid, std::map<QString, QVector2D>> *m_nodesCutFaces = nullptr; std::map<QUuid, std::map<QString, QVector2D>> *m_nodesCutFaces = nullptr;
quint64 m_id = 0; quint64 m_id = 0;
std::vector<QVector3D> m_clothCollisionVertices;
std::vector<std::vector<size_t>> m_clothCollisionTriangles;
void collectParts(); void collectParts();
bool checkIsComponentDirty(const QString &componentIdString); bool checkIsComponentDirty(const QString &componentIdString);
@ -125,7 +128,9 @@ private:
GeneratedComponent &componentCache); GeneratedComponent &componentCache);
MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true); MeshCombiner::Mesh *combineMultipleMeshes(const std::vector<std::tuple<MeshCombiner::Mesh *, CombineMode, QString>> &multipleMeshes, bool recombine=true);
QString componentColorName(const std::map<QString, QString> *component); QString componentColorName(const std::map<QString, QString> *component);
ComponentLayer componentLayer(const std::map<QString, QString> *component);
void collectUncombinedComponent(const QString &componentIdString); void collectUncombinedComponent(const QString &componentIdString);
void collectClothComponent(const QString &componentIdString);
void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate); void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector<QVector2D> &cutTemplate);
void remesh(const std::vector<OutcomeNode> &inputNodes, void remesh(const std::vector<OutcomeNode> &inputNodes,
const std::vector<QVector3D> &inputVertices, const std::vector<QVector3D> &inputVertices,

View File

@ -315,6 +315,17 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
} }
QWidget *widget = new QWidget; QWidget *widget = new QWidget;
if (nullptr != component) { if (nullptr != component) {
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) {
emit setComponentLayer(component->id, (ComponentLayer)index);
emit groupOperationAdded();
});
QComboBox *combineModeSelectBox = new QComboBox; QComboBox *combineModeSelectBox = new QComboBox;
for (size_t i = 0; i < (size_t)CombineMode::Count; ++i) { for (size_t i = 0; i < (size_t)CombineMode::Count; ++i) {
CombineMode mode = (CombineMode)i; CombineMode mode = (CombineMode)i;
@ -368,6 +379,7 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
if (nullptr != partTargetSelectBox) if (nullptr != partTargetSelectBox)
componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox); componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox);
componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox); componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox);
componentSettingsLayout->addRow(tr("Layer"), componentLayerSelectBox);
QVBoxLayout *newLayout = new QVBoxLayout; QVBoxLayout *newLayout = new QVBoxLayout;
newLayout->addLayout(layout); newLayout->addLayout(layout);

View File

@ -23,6 +23,7 @@ signals:
void setComponentSmoothAll(QUuid componentId, float toSmoothAll); void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam); void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
void setComponentPolyCount(QUuid componentId, PolyCount count); void setComponentPolyCount(QUuid componentId, PolyCount count);
void setComponentLayer(QUuid componentId, ComponentLayer layer);
void setPartTarget(QUuid partId, PartTarget target); void setPartTarget(QUuid partId, PartTarget target);
void setPartBase(QUuid partId, PartBase base); void setPartBase(QUuid partId, PartBase base);
void moveComponent(QUuid componentId, QUuid toParentId); void moveComponent(QUuid componentId, QUuid toParentId);

63
thirdparty/FastMassSpring/.gitattributes vendored Executable file
View File

@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

242
thirdparty/FastMassSpring/.gitignore vendored Executable file
View File

@ -0,0 +1,242 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Xx]64/
[Xx]86/
[Bb]uild/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Un-comment the next line if you do not want to checkin
# your web deploy settings because they may include unencrypted
# passwords
#*.pubxml
*.publishproj
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directory
AppPackages/
BundleArtifacts/
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# LightSwitch generated files
GeneratedArtifacts/
ModelManifest.xml
# Paket dependency manager
.paket/paket.exe
# FAKE - F# Make
.fake/

View File

@ -0,0 +1,204 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{bbca6691-6c35-4bfe-9cb9-13f16d29990f}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>ClothApp</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>$(SolutionDir)packages\nupengl.core.0.1.0.1\build\native\include;$(IncludePath)</IncludePath>
<Linkage-freeglut>static</Linkage-freeglut>
<Linkage-glew>static</Linkage-glew>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<IncludePath>C:\Program Files\Eigen\include;C:\Program Files\OpenMesh 7.1\include;$(IncludePath)</IncludePath>
<LibraryPath>C:\Program Files\OpenMesh 7.1\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>$(SolutionDir)packages\nupengl.core.0.1.0.1\build\native\include;$(IncludePath)</IncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IncludePath>C:\Program Files\Eigen\include;C:\Program Files\OpenMesh 7.1\include;$(IncludePath)</IncludePath>
<LibraryPath>C:\Program Files\OpenMesh 7.1\lib;$(LibraryPath)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>_USE_MATH_DEFINES;_SCL_SECURE_NO_WARNINGS</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>OpenMeshCored.lib;OpenMeshToolsd.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>_USE_MATH_DEFINES;_SCL_SECURE_NO_WARNINGS;NDEBUG</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>OpenMeshCore.lib;OpenMeshTools.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="shaders\phong.fshader">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</None>
<None Include="shaders\pick.fshader">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</None>
</ItemGroup>
<ItemGroup>
<ClCompile Include="App.cpp" />
<ClCompile Include="MassSpringSolver.cpp" />
<ClCompile Include="Mesh.cpp" />
<ClCompile Include="Renderer.cpp" />
<ClCompile Include="Shader.cpp" />
<ClCompile Include="UserInteraction.cpp" />
</ItemGroup>
<ItemGroup>
<FxCompile Include="shaders\basic.vshader">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</FxCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="MassSpringSolver.h" />
<ClInclude Include="Mesh.h" />
<ClInclude Include="Renderer.h" />
<ClInclude Include="Shader.h" />
<ClInclude Include="UserInteraction.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\nupengl.core.redist.0.1.0.1\build\native\nupengl.core.redist.targets" Condition="Exists('..\packages\nupengl.core.redist.0.1.0.1\build\native\nupengl.core.redist.targets')" />
<Import Project="..\packages\nupengl.core.0.1.0.1\build\native\nupengl.core.targets" Condition="Exists('..\packages\nupengl.core.0.1.0.1\build\native\nupengl.core.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\nupengl.core.redist.0.1.0.1\build\native\nupengl.core.redist.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\nupengl.core.redist.0.1.0.1\build\native\nupengl.core.redist.targets'))" />
<Error Condition="!Exists('..\packages\nupengl.core.0.1.0.1\build\native\nupengl.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\nupengl.core.0.1.0.1\build\native\nupengl.core.targets'))" />
</Target>
</Project>

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Shader Files">
<UniqueIdentifier>{55e50294-8ec8-4842-bc08-005ce41662d4}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="shaders\phong.fshader">
<Filter>Shader Files</Filter>
</None>
<None Include="shaders\pick.fshader">
<Filter>Shader Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ClCompile Include="App.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Renderer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Mesh.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MassSpringSolver.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="UserInteraction.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Shader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<FxCompile Include="shaders\basic.vshader">
<Filter>Shader Files</Filter>
</FxCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Mesh.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Renderer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="MassSpringSolver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="UserInteraction.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Shader.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@ -0,0 +1,384 @@
#include "MassSpringSolver.h"
#include <iostream>
// S Y S T E M //////////////////////////////////////////////////////////////////////////////////////
mass_spring_system::mass_spring_system(
unsigned int n_points,
unsigned int n_springs,
float time_step,
EdgeList spring_list,
VectorXf rest_lengths,
VectorXf stiffnesses,
VectorXf masses,
VectorXf fext,
float damping_factor
)
: n_points(n_points), n_springs(n_springs),
time_step(time_step), spring_list(spring_list),
rest_lengths(rest_lengths), stiffnesses(stiffnesses), masses(masses),
fext(fext), damping_factor(damping_factor) {}
// S O L V E R //////////////////////////////////////////////////////////////////////////////////////
MassSpringSolver::MassSpringSolver(mass_spring_system* system, float* vbuff)
: system(system), current_state(vbuff, system->n_points * 3),
prev_state(current_state), spring_directions(system->n_springs * 3) {
float h2 = system->time_step * system->time_step; // shorthand
// compute M, L, J
TripletList LTriplets, JTriplets;
// L
L.resize(3 * system->n_points, 3 * system->n_points);
unsigned int k = 0; // spring counter
for (Edge& i : system->spring_list) {
for (int j = 0; j < 3; j++) {
LTriplets.push_back(
Triplet(3 * i.first + j, 3 * i.first + j, 1 * system->stiffnesses[k]));
LTriplets.push_back(
Triplet(3 * i.first + j, 3 * i.second + j, -1 * system->stiffnesses[k]));
LTriplets.push_back(
Triplet(3 * i.second + j, 3 * i.first + j, -1 * system->stiffnesses[k]));
LTriplets.push_back(
Triplet(3 * i.second + j, 3 * i.second + j, 1 * system->stiffnesses[k]));
}
k++;
}
L.setFromTriplets(LTriplets.begin(), LTriplets.end());
// J
J.resize(3 * system->n_points, 3 * system->n_springs);
k = 0; // spring counter
for (Edge& i : system->spring_list) {
for (unsigned int j = 0; j < 3; j++) {
JTriplets.push_back(
Triplet(3 * i.first + j, 3 * k + j, 1 * system->stiffnesses[k]));
JTriplets.push_back(
Triplet(3 * i.second + j, 3 * k + j, -1 * system->stiffnesses[k]));
}
k++;
}
J.setFromTriplets(JTriplets.begin(), JTriplets.end());
// M
TripletList MTriplets;
M.resize(3 * system->n_points, 3 * system->n_points);
for (unsigned int i = 0; i < system->n_points; i++) {
for (int j = 0; j < 3; j++) {
MTriplets.push_back(Triplet(3 * i + j, 3 * i + j, system->masses[i]));
}
}
M.setFromTriplets(MTriplets.begin(), MTriplets.end());
// pre-factor system matrix
SparseMatrix A = M + h2 * L;
system_matrix.compute(A);
}
void MassSpringSolver::globalStep() {
float h2 = system->time_step * system->time_step; // shorthand
// compute right hand side
VectorXf b = inertial_term
+ h2 * J * spring_directions
+ h2 * system->fext;
// solve system and update state
current_state = system_matrix.solve(b);
}
void MassSpringSolver::localStep() {
unsigned int j = 0;
for (Edge& i : system->spring_list) {
Vector3f p12(
current_state[3 * i.first + 0] - current_state[3 * i.second + 0],
current_state[3 * i.first + 1] - current_state[3 * i.second + 1],
current_state[3 * i.first + 2] - current_state[3 * i.second + 2]
);
p12.normalize();
spring_directions[3 * j + 0] = system->rest_lengths[j] * p12[0];
spring_directions[3 * j + 1] = system->rest_lengths[j] * p12[1];
spring_directions[3 * j + 2] = system->rest_lengths[j] * p12[2];
j++;
}
}
void MassSpringSolver::solve(unsigned int n) {
float a = system->damping_factor; // shorthand
// update inertial term
inertial_term = M * ((a + 1) * (current_state) - a * prev_state);
// save current state in previous state
prev_state = current_state;
// perform steps
for (unsigned int i = 0; i < n; i++) {
localStep();
globalStep();
}
}
void MassSpringSolver::timedSolve(unsigned int ms) {
// TODO
}
// B U I L D E R ////////////////////////////////////////////////////////////////////////////////////
void MassSpringBuilder::uniformGrid(
unsigned int n,
float time_step,
float rest_length,
float stiffness,
float mass,
float damping_factor,
float gravity
) {
// n must be odd
assert(n % 2 == 1);
// shorthand
const double root2 = 1.41421356237;
// compute n_points and n_springs
unsigned int n_points = n * n;
unsigned int n_springs = (n - 1) * (5 * n - 2);
// build mass list
VectorXf masses(mass * VectorXf::Ones(n_springs));
// build spring list and spring parameters
EdgeList spring_list(n_springs);
structI.reserve(2 * (n - 1) * n);
shearI.reserve(2 * (n - 1) * (n - 1));
bendI.reserve(n * (n - 1));
VectorXf rest_lengths(n_springs);
VectorXf stiffnesses(n_springs);
unsigned int k = 0; // spring counter
for(unsigned int i = 0; i < n; i++) {
for(unsigned int j = 0; j < n; j++) {
// bottom right corner
if(i == n - 1 && j == n - 1) {
continue;
}
if (i == n - 1) {
// structural spring
spring_list[k] = Edge(n * i + j, n * i + j + 1);
rest_lengths[k] = rest_length;
stiffnesses[k] = stiffness;
structI.push_back(k++);
// bending spring
if (j % 2 == 0) {
spring_list[k] = Edge(n * i + j, n * i + j + 2);
rest_lengths[k] = 2 * rest_length;
stiffnesses[k] = stiffness;
bendI.push_back(k++);
}
continue;
}
// right edge
if (j == n - 1) {
// structural spring
spring_list[k] = Edge(n * i + j, n * (i + 1) + j);
rest_lengths[k] = rest_length;
stiffnesses[k] = stiffness;
structI.push_back(k++);
// bending spring
if (i % 2 == 0){
spring_list[k] = Edge(n * i + j, n * (i + 2) + j);
rest_lengths[k] = 2 * rest_length;
stiffnesses[k] = stiffness;
bendI.push_back(k++);
}
continue;
}
// structural springs
spring_list[k] = Edge(n * i + j, n * i + j + 1);
rest_lengths[k] = rest_length;
stiffnesses[k] = stiffness;
structI.push_back(k++);
spring_list[k] = Edge(n * i + j, n * (i + 1) + j);
rest_lengths[k] = rest_length;
stiffnesses[k] = stiffness;
structI.push_back(k++);
// shearing springs
spring_list[k] = Edge(n * i + j, n * (i + 1) + j + 1);
rest_lengths[k] = root2 * rest_length;
stiffnesses[k] = stiffness;
shearI.push_back(k++);
spring_list[k] = Edge(n * (i + 1) + j, n * i + j + 1);
rest_lengths[k] = root2 * rest_length;
stiffnesses[k] = stiffness;
shearI.push_back(k++);
// bending springs
if (j % 2 == 0) {
spring_list[k] = Edge(n * i + j, n * i + j + 2);
rest_lengths[k] = 2 * rest_length;
stiffnesses[k] = stiffness;
bendI.push_back(k++);
}
if (i % 2 == 0) {
spring_list[k] = Edge(n * i + j, n * (i + 2) + j);
rest_lengths[k] = 2 * rest_length;
stiffnesses[k] = stiffness;
bendI.push_back(k++);
}
}
}
// compute external forces
VectorXf fext = Vector3f(0, 0, -gravity).replicate(n_points, 1);
result = new mass_spring_system(n_points, n_springs, time_step, spring_list, rest_lengths,
stiffnesses, masses, fext, damping_factor);
}
MassSpringBuilder::IndexList MassSpringBuilder::getStructIndex() { return structI; }
MassSpringBuilder::IndexList MassSpringBuilder::getShearIndex() { return shearI; }
MassSpringBuilder::IndexList MassSpringBuilder::getBendIndex() { return bendI; }
mass_spring_system* MassSpringBuilder::getResult() { return result; }
// C O N S T R A I N T //////////////////////////////////////////////////////////////////////////////
CgNode::CgNode(mass_spring_system* system, float* vbuff) : system(system), vbuff(vbuff) {}
// point node
CgPointNode::CgPointNode(mass_spring_system* system, float* vbuff) : CgNode(system, vbuff) {}
bool CgPointNode::accept(CgNodeVisitor& visitor) { return visitor.visit(*this); }
// spring node
CgSpringNode::CgSpringNode(mass_spring_system* system, float* vbuff) : CgNode(system, vbuff) {}
bool CgSpringNode::accept(CgNodeVisitor& visitor) {
for (CgNode* child : children) {
if (!child->accept(visitor)) return false;
}
return visitor.visit(*this);
}
void CgSpringNode::addChild(CgNode* node) { children.push_back(node); }
void CgSpringNode::removeChild(CgNode* node) {
children.erase(find(children.begin(), children.end(), node));
}
// root node
CgRootNode::CgRootNode(mass_spring_system* system, float* vbuff) : CgSpringNode(system, vbuff) {}
void CgRootNode::satisfy() { return; }
bool CgRootNode::accept(CgNodeVisitor& visitor) {
for (CgNode* child : children) {
if (!child->accept(visitor)) return false;
}
return true;
}
// point fix node
CgPointFixNode::CgPointFixNode(mass_spring_system* system, float* vbuff)
: CgPointNode(system, vbuff) {}
bool CgPointFixNode::query(unsigned int i) const { return fix_map.find(3 * i) != fix_map.end(); }
void CgPointFixNode::satisfy() {
for (auto fix : fix_map)
for (int i = 0; i < 3; i++)
vbuff[fix.first + i] = fix.second[i];
}
void CgPointFixNode::fixPoint(unsigned int i) {
assert(i >= 0 && i < system->n_points);
fix_map[3 * i] = Vector3f(vbuff[3 * i], vbuff[3 * i + 1], vbuff[3 * i + 2]);
}
void CgPointFixNode::releasePoint(unsigned int i) { fix_map.erase(3 * i); }
// spring deformation node
CgSpringDeformationNode::CgSpringDeformationNode(mass_spring_system* system, float* vbuff,
float tauc, unsigned int n_iter) : CgSpringNode(system, vbuff), tauc(tauc), n_iter(n_iter) {}
void CgSpringDeformationNode::satisfy() {
for (int k = 0; k < n_iter; k++) {
for (unsigned int i : items) {
Edge spring = system->spring_list[i];
CgQueryFixedPointVisitor visitor;
Vector3f p12(
vbuff[3 * spring.first + 0] - vbuff[3 * spring.second + 0],
vbuff[3 * spring.first + 1] - vbuff[3 * spring.second + 1],
vbuff[3 * spring.first + 2] - vbuff[3 * spring.second + 2]
);
float len = p12.norm();
float rlen = system->rest_lengths[i];
float diff = (len - (1 + tauc) * rlen) / len;
float rate = (len - rlen) / rlen;
// check deformation
if (rate <= tauc) continue;
// check if points are fixed
float f1, f2;
f1 = f2 = 0.5f;
// if first point is fixed
if (visitor.queryPoint(*this, spring.first)) { f1 = 0.0f; f2 = 1.0f; }
// if second point is fixed
if (visitor.queryPoint(*this, spring.second)) {
f1 = (f1 != 0.0f ? 1.0f : 0.0f);
f2 = 0.0f;
}
for (int j = 0; j < 3; j++) {
vbuff[3 * spring.first + j] -= p12[j] * f1 * diff;
vbuff[3 * spring.second + j] += p12[j] * f2 * diff;
}
}
}
}
void CgSpringDeformationNode::addSprings(std::vector<unsigned int> springs) {
items.insert(springs.begin(), springs.end());
}
// sphere collision node
CgSphereCollisionNode::CgSphereCollisionNode(mass_spring_system* system, float* vbuff,
float radius, Vector3f center) : CgPointNode(system, vbuff), radius(radius), center(center) {}
bool CgSphereCollisionNode::query(unsigned int i) const { return false; }
void CgSphereCollisionNode::satisfy() {
for (int i = 0; i < system->n_points; i++) {
Vector3f p(
vbuff[3 * i + 0] - center[0],
vbuff[3 * i + 1] - center[1],
vbuff[3 * i + 2] - center[2]
);
if (p.norm() < radius) {
p.normalize();
p = radius * p;
}
else continue;
for (int j = 0; j < 3; j++) {
vbuff[3 * i + j] = p[j] + center[j];
}
}
}
// node visitor
bool CgNodeVisitor::visit(CgPointNode& node) { return true; }
bool CgNodeVisitor::visit(CgSpringNode& node) { return true; }
// query fixed point visitor
bool CgQueryFixedPointVisitor::visit(CgPointNode& node) {
queryResult = node.query(i);
return !queryResult;
}
bool CgQueryFixedPointVisitor::queryPoint(CgNode& root, unsigned int i) {
this->i = i;
root.accept(*this);
return queryResult;
}
// satisfy visitor
bool CgSatisfyVisitor::visit(CgPointNode& node) { node.satisfy(); return true; }
bool CgSatisfyVisitor::visit(CgSpringNode& node) { node.satisfy(); return true; }
void CgSatisfyVisitor::satisfy(CgNode& root) { root.accept(*this); }

View File

@ -0,0 +1,230 @@
#pragma once
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <vector>
#include <unordered_map>
#include <unordered_set>
// Mass-Spring System struct
struct mass_spring_system {
typedef Eigen::SparseMatrix<float> SparseMatrix;
typedef Eigen::VectorXf VectorXf;
typedef std::pair<unsigned int, unsigned int> Edge;
typedef std::vector<Edge> EdgeList;
// parameters
unsigned int n_points; // number of points
unsigned int n_springs; // number of springs
float time_step; // time step
EdgeList spring_list; // spring edge list
VectorXf rest_lengths; // spring rest lengths
VectorXf stiffnesses; // spring stiffnesses
VectorXf masses; // point masses
VectorXf fext; // external forces
float damping_factor; // damping factor
mass_spring_system(
unsigned int n_points, // number of points
unsigned int n_springs, // number of springs
float time_step, // time step
EdgeList spring_list, // spring edge list
VectorXf rest_lengths, // spring rest lengths
VectorXf stiffnesses, // spring stiffnesses
VectorXf masses, // point masses
VectorXf fext, // external forces
float damping_factor // damping factor
);
};
// Mass-Spring System Solver class
class MassSpringSolver {
private:
typedef Eigen::Vector3f Vector3f;
typedef Eigen::VectorXf VectorXf;
typedef Eigen::SparseMatrix<float> SparseMatrix;
typedef Eigen::SimplicialLLT<Eigen::SparseMatrix<float> > Cholesky;
typedef Eigen::Map<Eigen::VectorXf> Map;
typedef std::pair<unsigned int, unsigned int> Edge;
typedef Eigen::Triplet<float> Triplet;
typedef std::vector<Triplet> TripletList;
// system
mass_spring_system* system;
Cholesky system_matrix;
// M, L, J matrices
SparseMatrix M;
SparseMatrix L;
SparseMatrix J;
// state
Map current_state; // q(n), current state
VectorXf prev_state; // q(n - 1), previous state
VectorXf spring_directions; // d, spring directions
VectorXf inertial_term; // M * y, y = (a + 1) * q(n) - a * q(n - 1)
// steps
void globalStep();
void localStep();
public:
MassSpringSolver(mass_spring_system* system, float* vbuff);
// solve iterations
void solve(unsigned int n);
void timedSolve(unsigned int ms);
};
// Mass-Spring System Builder Class
class MassSpringBuilder {
private:
typedef Eigen::Vector3f Vector3f;
typedef Eigen::VectorXf VectorXf;
typedef std::pair<unsigned int, unsigned int> Edge;
typedef std::vector<Edge> EdgeList;
typedef Eigen::Triplet<float> Triplet;
typedef std::vector<Triplet> TripletList;
typedef std::vector<unsigned int> IndexList;
IndexList structI, shearI, bendI;
mass_spring_system* result;
public:
void uniformGrid(
unsigned int n, // grid width
float time_step, // time step
float rest_length, // spring rest length (non-diagonal)
float stiffness, // spring stiffness
float mass, // node mass
float damping_factor, // damping factor
float gravity // gravitationl force (-z axis)
);
// indices
IndexList getStructIndex(); // structural springs
IndexList getShearIndex(); // shearing springs
IndexList getBendIndex(); // bending springs
mass_spring_system* getResult();
};
// Constraint Graph
class CgNodeVisitor; // Constraint graph node visitor
// Constraint graph node
class CgNode {
protected:
mass_spring_system* system;
float* vbuff;
public:
CgNode(mass_spring_system* system, float* vbuff);
virtual void satisfy() = 0; // satisfy constraint
virtual bool accept(CgNodeVisitor& visitor) = 0; // accept visitor
};
// point constraint node
class CgPointNode : public CgNode {
public:
CgPointNode(mass_spring_system* system, float* vbuff);
virtual bool query(unsigned int i) const = 0; // check if point with index i is constrained
virtual bool accept(CgNodeVisitor& visitor);
};
// spring constraint node
class CgSpringNode : public CgNode {
protected:
typedef std::vector<CgNode*> NodeList;
NodeList children;
public:
CgSpringNode(mass_spring_system* system, float* vbuff);
virtual bool accept(CgNodeVisitor& visitor);
void addChild(CgNode* node);
void removeChild(CgNode* node);
};
// root node
class CgRootNode : public CgSpringNode {
public:
CgRootNode(mass_spring_system* system, float* vbuff);
virtual void satisfy();
virtual bool accept(CgNodeVisitor& visitor);
};
class CgPointFixNode : public CgPointNode {
protected:
typedef Eigen::Vector3f Vector3f;
std::unordered_map<unsigned int, Vector3f> fix_map;
public:
CgPointFixNode(mass_spring_system* system, float* vbuff);
virtual void satisfy();
virtual bool query(unsigned int i) const;
virtual void fixPoint(unsigned int i); // add point at index i to list
virtual void releasePoint(unsigned int i); // remove point at index i from list
};
// spring node
class CgSpringDeformationNode : public CgSpringNode {
private:
typedef std::pair<unsigned int, unsigned int> Edge;
typedef Eigen::Vector3f Vector3f;
std::unordered_set<unsigned int> items;
float tauc; // critical deformation rate
unsigned int n_iter; // number of iterations
public:
CgSpringDeformationNode(mass_spring_system* system, float* vbuff, float tauc, unsigned int n_iter);
virtual void satisfy();
void addSprings(std::vector<unsigned int> springs);
};
// sphere collision node
class CgSphereCollisionNode : public CgPointNode {
private:
typedef Eigen::Vector3f Vector3f;
float radius;
Vector3f center;
public:
CgSphereCollisionNode(mass_spring_system* system, float* vbuff, float radius, Vector3f center);
virtual bool query(unsigned int i) const;
virtual void satisfy();
};
// node visitor
class CgNodeVisitor {
public:
virtual bool visit(CgPointNode& node);
virtual bool visit(CgSpringNode& node);
};
// fixed point query visitor
class CgQueryFixedPointVisitor : public CgNodeVisitor {
private:
unsigned int i;
bool queryResult;
public:
virtual bool visit(CgPointNode& node);
bool queryPoint(CgNode& root, unsigned int i);
};
// satisfy visitor
class CgSatisfyVisitor : public CgNodeVisitor {
public:
virtual bool visit(CgPointNode& node);
virtual bool visit(CgSpringNode& node);
void satisfy(CgNode& root);
};

76
thirdparty/FastMassSpring/ClothApp/Mesh.cpp vendored Executable file
View File

@ -0,0 +1,76 @@
#include "Mesh.h"
// M E S H /////////////////////////////////////////////////////////////////////////////////////
float* Mesh::vbuff() { return VERTEX_DATA(this); }
float* Mesh::nbuff() { return NORMAL_DATA(this); }
float* Mesh::tbuff() { return TEXTURE_DATA(this); }
unsigned int* Mesh::ibuff() { return &_ibuff[0]; }
void Mesh::useIBuff(std::vector<unsigned int>& _ibuff) { this->_ibuff = _ibuff; }
unsigned int Mesh::vbuffLen() { return (unsigned int)n_vertices() * 3; }
unsigned int Mesh::nbuffLen() { return (unsigned int)n_vertices() * 3; }
unsigned int Mesh::tbuffLen() { return (unsigned int)n_vertices() * 2; }
unsigned int Mesh::ibuffLen() { return (unsigned int)_ibuff.size(); }
// M E S H B U I L D E R /////////////////////////////////////////////////////////////////////
void MeshBuilder::uniformGrid(float w, int n) {
result = new Mesh;
unsigned int ibuffLen = 6 * (n - 1) * (n - 1);
std::vector<unsigned int> ibuff(ibuffLen);
// request mesh properties
result->request_vertex_normals();
result->request_vertex_normals();
result->request_vertex_texcoords2D();
// generate mesh
unsigned int idx = 0; // vertex index
const float d = w / (n - 1); // step distance
const float ud = 1.0f / (n - 1); // unit step distance
const OpenMesh::Vec3f o = OpenMesh::Vec3f(-w/2.0f, w/2.0f, 0.0f); // origin
const OpenMesh::Vec3f ux = OpenMesh::Vec3f(1.0f, 0.0f, 0.0f); // unit x direction
const OpenMesh::Vec3f uy = OpenMesh::Vec3f(0.0f, -1.0f, 0.0f); // unit y direction
std::vector<OpenMesh::VertexHandle> handle_table(n * n); // table storing vertex handles for easy grid connectivity establishment
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
handle_table[j + i * n] = result->add_vertex(o + d*j*ux + d*i*uy); // add vertex
result->set_texcoord2D(handle_table[j + i * n], OpenMesh::Vec2f(ud*j, ud*i)); // add texture coordinates
//add connectivity
if (i > 0 && j < n - 1) {
result->add_face(
handle_table[j + i * n],
handle_table[j + 1 + (i - 1) * n],
handle_table[j + (i - 1) * n]
);
ibuff[idx++] = j + i * n;
ibuff[idx++] = j + 1 + (i - 1) * n;
ibuff[idx++] = j + (i - 1) * n;
}
if (j > 0 && i > 0) {
result->add_face(
handle_table[j + i * n],
handle_table[j + (i - 1) * n],
handle_table[j - 1 + i * n]
);
ibuff[idx++] = j + i * n;
ibuff[idx++] = j + (i - 1) * n;
ibuff[idx++] = j - 1 + i * n;
}
}
}
// calculate normals
result->request_face_normals();
result->update_normals();
result->release_face_normals();
// set index buffer
result->useIBuff(ibuff);
}
Mesh* MeshBuilder::getResult() { return result; }

42
thirdparty/FastMassSpring/ClothApp/Mesh.h vendored Executable file
View File

@ -0,0 +1,42 @@
#pragma once
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <vector>
// Mesh type
typedef OpenMesh::TriMesh_ArrayKernelT<> _Mesh;
// Macros for extracting buffers from OpenMesh
#define VERTEX_DATA(mesh) (float*) &(mesh->point(*mesh->vertices_begin()))
#define NORMAL_DATA(mesh) (float*) &(mesh->normal(*mesh->vertices_begin()))
#define TEXTURE_DATA(mesh) (float*) &(mesh->texcoord2D(*mesh->vertices_begin()))
// Mesh class
class Mesh : public _Mesh {
private:
std::vector<unsigned int> _ibuff;
public:
// pointers to buffers
float* vbuff();
float* nbuff();
float* tbuff();
unsigned int* ibuff();
// buffer sizes
unsigned int vbuffLen();
unsigned int nbuffLen();
unsigned int tbuffLen();
unsigned int ibuffLen();
// set index buffer
void useIBuff(std::vector<unsigned int>& _ibuff);
};
class MeshBuilder {
private:
Mesh* result;
public:
void uniformGrid(float w, int n);
Mesh* getResult();
};

View File

@ -0,0 +1,49 @@
#include "Renderer.h"
#include <glm/gtc/type_ptr.hpp>
Renderer::Renderer() {}
void Renderer::setProgram(GLProgram* program) {
assert(program != nullptr);
assert((*program) > 0);
this->program = program;
}
void Renderer::setProgramInput(ProgramInput* input) {
this->input = input;
}
void Renderer::setModelview(const glm::mat4& mv) {
assert(program != nullptr);
assert((*program) > 0);
glUseProgram(*program);
program->setModelView(mv);
glUseProgram(0);
}
void Renderer::setProjection(const glm::mat4& p) {
assert(program != nullptr);
assert((*program) > 0);
glUseProgram(*program);
program->setProjection(p);
glUseProgram(0);
}
void Renderer::setElementCount(unsigned int n_elements) { this->n_elements = n_elements; }
void Renderer::draw() {
assert(program != nullptr);
assert((*program) > 0);
glUseProgram(*program);
glBindVertexArray(*input);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glDrawElements(GL_TRIANGLES, n_elements, GL_UNSIGNED_INT, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glBindVertexArray(0);
glUseProgram(0);
}

22
thirdparty/FastMassSpring/ClothApp/Renderer.h vendored Executable file
View File

@ -0,0 +1,22 @@
#pragma once
#include <glm/gtc/matrix_transform.hpp>
#include "Shader.h"
class Renderer {
protected:
GLProgram* program;
ProgramInput* input;
unsigned int n_elements;
public:
Renderer();
void setProgram(GLProgram* program);
void setProgramInput(ProgramInput* input);
void setModelview(const glm::mat4& mv);
void setProjection(const glm::mat4& p);
void setElementCount(unsigned int n_elements);
void draw();
};

190
thirdparty/FastMassSpring/ClothApp/Shader.cpp vendored Executable file
View File

@ -0,0 +1,190 @@
#include "Shader.h"
#include <vector>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
// GLSHADER ///////////////////////////////////////////////////////////////////////////////////
GLShader::GLShader(GLenum shaderType) : handle(glCreateShader(shaderType)) {};
GLShader::operator GLuint() const {
return handle;
}
GLShader::~GLShader() {
glDeleteShader(handle);
}
void GLShader::compile(const char* source) {
GLint compiled = 0; // Compiled flag
const char *ptrs[] = { source };
const GLint lens[] = { std::strlen(source) };
glShaderSource(handle, 1, ptrs, lens);
glCompileShader(handle);
glGetShaderiv(handle, GL_COMPILE_STATUS, &compiled);
if (!compiled) {
GLint logSize = 0;
glGetShaderiv(handle, GL_INFO_LOG_LENGTH, &logSize);
std::vector<GLchar> errorLog(logSize);
glGetShaderInfoLog(handle, logSize, &logSize, &errorLog[0]);
std::cerr << &errorLog[0] << std::endl;
throw std::runtime_error("Failed to compile shader.");
}
}
void GLShader::compile(std::ifstream& source) {
std::vector<char> text;
source.seekg(0, std::ios_base::end);
std::streampos fileSize = source.tellg();
text.resize(fileSize);
source.seekg(0, std::ios_base::beg);
source.read(&text[0], fileSize);
compile(&text[0]);
}
// GLPROGRAM //////////////////////////////////////////////////////////////////////////////////
GLProgram::GLProgram() : handle(glCreateProgram()) {}
void GLProgram::link(const GLShader& vshader, const GLShader& fshader) {
GLint linked = 0; // Linked flag
glAttachShader(handle, vshader);
glAttachShader(handle, fshader);
glLinkProgram(handle);
glDetachShader(handle, vshader);
glDetachShader(handle, fshader);
glGetProgramiv(handle, GL_LINK_STATUS, &linked);
if (!linked)
throw std::runtime_error("Failed to link shaders.");
// get camera uniforms
uModelViewMatrix = glGetUniformLocation(handle, "uModelViewMatrix");
uProjectionMatrix = glGetUniformLocation(handle, "uProjectionMatrix");
// post link
postLink();
}
void GLProgram::postLink() {}
void GLProgram::setUniformMat4(GLuint unif, glm::mat4 m) {
glUseProgram(handle);
glUniformMatrix4fv(unif,
1, GL_FALSE, glm::value_ptr(m[0]));
glUseProgram(0);
}
void GLProgram::setModelView(glm::mat4 m) {
setUniformMat4(uModelViewMatrix, m);
}
void GLProgram::setProjection(glm::mat4 m) {
setUniformMat4(uProjectionMatrix, m);
}
GLProgram::operator GLuint() const { return handle; }
GLProgram::~GLProgram() { glDeleteProgram(handle); }
// PROGRAM INPUT //////////////////////////////////////////////////////////////////////////////
ProgramInput::ProgramInput() {
// generate buffers
glGenBuffers(4, &vbo[0]);
// generate vertex array object
glGenVertexArrays(1, &handle);
glBindVertexArray(handle);
// positions
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
// normals
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
// texture coordinates
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
// indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glBindVertexArray(0);
}
void ProgramInput::bufferData(unsigned int index, void* buff, size_t size) {
glBindBuffer(GL_ARRAY_BUFFER, vbo[index]);
glBufferData(GL_ARRAY_BUFFER, size,
buff, GL_STATIC_DRAW);
}
void ProgramInput::setPositionData(float* buff, unsigned int len) {
bufferData(0, buff, sizeof(float) * len);
}
void ProgramInput::setNormalData(float* buff, unsigned int len) {
bufferData(1, buff, sizeof(float) * len);
}
void ProgramInput::setTextureData(float* buff, unsigned int len) {
bufferData(2, buff, sizeof(float) * len);
}
void ProgramInput::setIndexData(unsigned int* buff, unsigned int len) {
bufferData(3, buff, sizeof(unsigned int) * len);
}
ProgramInput::operator GLuint() const {
return handle;
}
ProgramInput::~ProgramInput() {
glDeleteBuffers(4, vbo);
glDeleteVertexArrays(1, &handle);
}
// SHADER PROGRAMS ////////////////////////////////////////////////////////////////////////////
PhongShader::PhongShader() : GLProgram() {}
void PhongShader::postLink() {
// get uniforms
uAlbedo = glGetUniformLocation(handle, "uAlbedo");
uAmbient = glGetUniformLocation(handle, "uAmbient");
uLight = glGetUniformLocation(handle, "uLight");
}
void PhongShader::setAlbedo(const glm::vec3& albedo) {
assert(uAlbedo >= 0);
glUseProgram(*this);
glUniform3f(uAlbedo, albedo[0], albedo[1], albedo[2]);
glUseProgram(0);
}
void PhongShader::setAmbient(const glm::vec3& ambient) {
assert(uAmbient >= 0);
glUseProgram(*this);
glUniform3f(uAmbient, ambient[0], ambient[1], ambient[2]);
glUseProgram(0);
}
void PhongShader::setLight(const glm::vec3& light) {
assert(uLight >= 0);
glUseProgram(*this);
glUniform3f(uLight, light[0], light[1], light[2]);
glUseProgram(0);
}
PickShader::PickShader() : GLProgram() {}
void PickShader::postLink() {
// get uniforms
uTessFact = glGetUniformLocation(handle, "uTessFact");
}
void PickShader::setTessFact(unsigned int n) {
assert(uTessFact >= 0);
glUseProgram(*this);
glUniform1i(uTessFact, n);
glUseProgram(0);
}

87
thirdparty/FastMassSpring/ClothApp/Shader.h vendored Executable file
View File

@ -0,0 +1,87 @@
#pragma once
#include <fstream>
#include <GL\glew.h>
#include <glm/glm.hpp>
class NonCopyable {
private:
NonCopyable(const NonCopyable& other) = delete;
NonCopyable& operator=(const NonCopyable& other) = delete;
public:
NonCopyable() {}
};
class GLShader : public NonCopyable {
private:
GLuint handle; // Shader handle
public:
GLShader(GLenum shaderType);
/*GLShader(GLenum shaderType, const char* source);
GLShader(GLenum shaderType, std::ifstream& source);*/
void compile(const char* source);
void compile(std::ifstream& source);
operator GLuint() const; // cast to GLuint
~GLShader();
};
class GLProgram : public NonCopyable {
protected:
GLuint handle;
GLuint uModelViewMatrix, uProjectionMatrix;
void setUniformMat4(GLuint unif, glm::mat4 m);
public:
GLProgram();
virtual void link(const GLShader& vshader, const GLShader& fshader);
virtual void postLink();
operator GLuint() const; // cast to GLuint
void setModelView(glm::mat4 m);
void setProjection(glm::mat4 m);
~GLProgram();
};
class ProgramInput : public NonCopyable {
private:
GLuint handle; // vertex array object handle
GLuint vbo[4]; // vertex buffer object handles | position, normal, texture, index
void bufferData(unsigned int index, void* buff, size_t size);
public:
ProgramInput();
void setPositionData(float* buff, unsigned int len);
void setNormalData(float* buff, unsigned int len);
void setTextureData(float* buff, unsigned int len);
void setIndexData(unsigned int* buff, unsigned int len);
operator GLuint() const; // cast to GLuint
~ProgramInput();
};
class PhongShader : public GLProgram {
private:
// Albedo | Ambient Light | Light Direction
GLuint uAlbedo, uAmbient, uLight;
public:
PhongShader();
virtual void postLink();
void setAlbedo(const glm::vec3& albedo);
void setAmbient(const glm::vec3& ambient);
void setLight(const glm::vec3& light);
};
class PickShader : public GLProgram {
private:
GLuint uTessFact;
public:
PickShader();
virtual void postLink();
void setTessFact(unsigned int n);
};

View File

@ -0,0 +1,48 @@
#include "UserInteraction.h"
#include <GL/glew.h>
#include <iostream>
#include <cmath>
UserInteraction::UserInteraction(Renderer* renderer, CgPointFixNode* fixer, float* vbuff)
: renderer(renderer), vbuff(vbuff), fixer(fixer), i(-1) {}
void UserInteraction::setModelview(const glm::mat4& mv) { renderer->setModelview(mv); }
void UserInteraction::setProjection(const glm::mat4& p) { renderer->setProjection(p); }
void UserInteraction::grabPoint(int mouse_x, int mouse_y){
// render scene
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_FRAMEBUFFER_SRGB);
renderer->draw();
glFlush();
// read color
color c(3);
glReadPixels(mouse_x, mouse_y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &c[0]);
i = colorToIndex(c);
if (i != -1) fixer->fixPoint(i);
// return to normal state
glClearColor(0.25f, 0.25f, 0.25f, 0);
glEnable(GL_FRAMEBUFFER_SRGB);
}
void UserInteraction::releasePoint() { if (i == -1) return; fixer->releasePoint(i); i = -1; }
void UserInteraction::movePoint(vec3 v) {
if (i == -1) return;
fixer->releasePoint(i);
for(int j = 0; j < 3; j++)
vbuff[3 * i + j] += v[j];
fixer->fixPoint(i);
}
GridMeshUI::GridMeshUI(Renderer* renderer, CgPointFixNode* fixer, float* vbuff, unsigned int n)
: UserInteraction(renderer, fixer, vbuff), n(n) {}
int GridMeshUI::colorToIndex(color c) const {
if (c[2] != 51) return -1;
int vx = std::round((n - 1) * c[0] / 255.0);
int vy = std::round((n - 1) * c[1] / 255.0);
return n * vy + vx;
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <glm/common.hpp>
#include "MassSpringSolver.h"
#include "Renderer.h"
class UserInteraction {
protected:
typedef glm::vec3 vec3;
typedef std::vector<unsigned char> color;
int i; // index of fixed point
float* vbuff; // vertex buffer
CgPointFixNode* fixer; // point fixer
Renderer* renderer; // pick shader renderer
virtual int colorToIndex(color c) const = 0;
public:
UserInteraction(Renderer* renderer, CgPointFixNode* fixer, float* vbuff);
void setModelview(const glm::mat4& mv);
void setProjection(const glm::mat4& p);
void grabPoint(int mouse_x, int mouse_y); // grab point with color c
void movePoint(vec3 v); // move grabbed point along mouse
void releasePoint(); // release grabbed point;
};
class GridMeshUI : public UserInteraction {
protected:
const unsigned int n; // grid width
virtual int colorToIndex(color c) const;
public:
GridMeshUI(Renderer* renderer, CgPointFixNode* fixer, float* vbuff, unsigned int n);
};

471
thirdparty/FastMassSpring/ClothApp/app.cpp vendored Executable file
View File

@ -0,0 +1,471 @@
#include <GL/glew.h>
#include <GL/glut.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <stdexcept>
#include <iostream>
#include <string>
#include <vector>
#include "Shader.h"
#include "Mesh.h"
#include "Renderer.h"
#include "MassSpringSolver.h"
#include "UserInteraction.h"
// G L O B A L S ///////////////////////////////////////////////////////////////////
// Window
static int g_windowWidth = 640, g_windowHeight = 640;
static bool g_mouseClickDown = false;
static bool g_mouseLClickButton, g_mouseRClickButton, g_mouseMClickButton;
static int g_mouseClickX;
static int g_mouseClickY;
// User Interaction
static UserInteraction* UI;
static Renderer* g_pickRenderer;
// Constants
static const float PI = glm::pi<float>();
// Shader Handles
static PhongShader* g_phongShader; // linked phong shader
static PickShader* g_pickShader; // linked pick shader
// Shader parameters
static const glm::vec3 g_albedo(0.0f, 0.3f, 0.7f);
static const glm::vec3 g_ambient(0.01f, 0.01f, 0.01f);
static const glm::vec3 g_light(1.0f, 1.0f, -1.0f);
// Mesh
static Mesh* g_clothMesh; // halfedge data structure
// Render Target
static ProgramInput* g_render_target; // vertex, normal, texutre, index
// Animation
static const int g_fps = 60; // frames per second | 60
static const int g_iter = 5; // iterations per time step | 10
static const int g_frame_time = 15; // approximate time for frame calculations | 15
static const int g_animation_timer = (int) ((1.0f / g_fps) * 1000 - g_frame_time);
// Mass Spring System
static mass_spring_system* g_system;
static MassSpringSolver* g_solver;
// System parameters
namespace SystemParam {
static const int n = 61; // must be odd, n * n = n_vertices | 61
static const float w = 2.0f; // width | 2.0f
static const float h = 0.008f; // time step, smaller for better results | 0.008f = 0.016f/2
static const float r = w / (n - 1) * 1.05f; // spring rest legnth
static const float k = 1.0f; // spring stiffness | 1.0f;
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
}
// Constraint Graph
static CgRootNode* g_cgRootNode;
// Scene parameters
static const float g_camera_distance = 4.2f;
// Scene matrices
static glm::mat4 g_ModelViewMatrix;
static glm::mat4 g_ProjectionMatrix;
// F U N C T I O N S //////////////////////////////////////////////////////////////
// state initialization
static void initGlutState(int, char**);
static void initGLState();
static void initShaders(); // Read, compile and link shaders
static void initCloth(); // Generate cloth mesh
static void initScene(); // Generate scene matrices
// demos
static void demo_hang(); // curtain hanging from top corners
static void demo_drop(); // curtain dropping on sphere
static void(*g_demo)() = demo_drop;
// glut callbacks
static void display();
static void reshape(int, int);
static void mouse(int, int, int, int);
static void motion(int, int);
// draw cloth function
static void drawCloth();
static void animateCloth(int value);
// scene update
static void updateProjection();
static void updateRenderTarget();
// cleaning
static void cleanUp();
// error checks
void checkGlErrors();
// M A I N //////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv) {
try {
initGlutState(argc, argv);
glewInit();
initGLState();
initShaders();
initCloth();
initScene();
glutTimerFunc(g_animation_timer, animateCloth, 0);
glutMainLoop();
cleanUp();
return 0;
}
catch (const std::runtime_error& e) {
std::cout << "Exception caught: " << e.what() << std::endl;
return -1;
}
}
// S T A T E I N I T I A L I Z A T O N /////////////////////////////////////////////
static void initGlutState(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(g_windowWidth, g_windowHeight);
glutCreateWindow("Cloth App");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMotionFunc(motion);
}
static void initGLState() {
glClearColor(0.25f, 0.25f, 0.25f, 0);
glClearDepth(1.);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glReadBuffer(GL_BACK);
glEnable(GL_FRAMEBUFFER_SRGB);
checkGlErrors();
}
static void initShaders() {
GLShader basic_vert(GL_VERTEX_SHADER);
GLShader phong_frag(GL_FRAGMENT_SHADER);
GLShader pick_frag(GL_FRAGMENT_SHADER);
basic_vert.compile(std::ifstream("./shaders/basic.vshader"));
phong_frag.compile(std::ifstream("./shaders/phong.fshader"));
pick_frag.compile(std::ifstream("./shaders/pick.fshader"));
g_phongShader = new PhongShader;
g_pickShader = new PickShader;
g_phongShader->link(basic_vert, phong_frag);
g_pickShader->link(basic_vert, pick_frag);
checkGlErrors();
}
static void initCloth() {
// short hand
const int n = SystemParam::n;
const float w = SystemParam::w;
// generate mesh
MeshBuilder meshBuilder;
meshBuilder.uniformGrid(w, n);
g_clothMesh = meshBuilder.getResult();
// fill program input
g_render_target = new ProgramInput;
g_render_target->setPositionData(g_clothMesh->vbuff(), g_clothMesh->vbuffLen());
g_render_target->setNormalData(g_clothMesh->nbuff(), g_clothMesh->nbuffLen());
g_render_target->setTextureData(g_clothMesh->tbuff(), g_clothMesh->tbuffLen());
g_render_target->setIndexData(g_clothMesh->ibuff(), g_clothMesh->ibuffLen());
// check errors
checkGlErrors();
// build demo system
g_demo();
}
static void initScene() {
g_ModelViewMatrix = glm::lookAt(
glm::vec3(0.618, -0.786, 0.3f) * g_camera_distance,
glm::vec3(0.0f, 0.0f, -1.0f),
glm::vec3(0.0f, 0.0f, 1.0f)
) * glm::translate(glm::mat4(1), glm::vec3(0.0f, 0.0f, SystemParam::w / 4));
updateProjection();
}
static void demo_hang() {
// short hand
const int n = SystemParam::n;
// initialize mass spring system
MassSpringBuilder massSpringBuilder;
massSpringBuilder.uniformGrid(
SystemParam::n,
SystemParam::h,
SystemParam::r,
SystemParam::k,
SystemParam::m,
SystemParam::a,
SystemParam::g
);
g_system = massSpringBuilder.getResult();
// initialize mass spring solver
g_solver = new MassSpringSolver(g_system, g_clothMesh->vbuff());
// deformation constraint parameters
const float tauc = 0.4f; // critical spring deformation | 0.4f
const unsigned int deformIter = 15; // number of iterations | 15
// initialize constraints
// spring deformation constraint
CgSpringDeformationNode* deformationNode =
new CgSpringDeformationNode(g_system, g_clothMesh->vbuff(), tauc, deformIter);
deformationNode->addSprings(massSpringBuilder.getShearIndex());
deformationNode->addSprings(massSpringBuilder.getStructIndex());
// fix top corners
CgPointFixNode* cornerFixer = new CgPointFixNode(g_system, g_clothMesh->vbuff());
cornerFixer->fixPoint(0);
cornerFixer->fixPoint(n - 1);
// initialize user interaction
g_pickRenderer = new Renderer();
g_pickRenderer->setProgram(g_pickShader);
g_pickRenderer->setProgramInput(g_render_target);
g_pickRenderer->setElementCount(g_clothMesh->ibuffLen());
g_pickShader->setTessFact(SystemParam::n);
CgPointFixNode* mouseFixer = new CgPointFixNode(g_system, g_clothMesh->vbuff());
UI = new GridMeshUI(g_pickRenderer, mouseFixer, g_clothMesh->vbuff(), n);
// build constraint graph
g_cgRootNode = new CgRootNode(g_system, g_clothMesh->vbuff());
// first layer
g_cgRootNode->addChild(deformationNode);
// second layer
deformationNode->addChild(cornerFixer);
deformationNode->addChild(mouseFixer);
}
static void demo_drop() {
// short hand
const int n = SystemParam::n;
// initialize mass spring system
MassSpringBuilder massSpringBuilder;
massSpringBuilder.uniformGrid(
SystemParam::n,
SystemParam::h,
SystemParam::r,
SystemParam::k,
SystemParam::m,
SystemParam::a,
SystemParam::g
);
g_system = massSpringBuilder.getResult();
// initialize mass spring solver
g_solver = new MassSpringSolver(g_system, g_clothMesh->vbuff());
// sphere collision constraint parameters
const float radius = 0.64f; // sphere radius | 0.64f
const Eigen::Vector3f center(0, 0, -1);// sphere center | (0, 0, -1)
// deformation constraint parameters
const float tauc = 0.12f; // critical spring deformation | 0.12f
const unsigned int deformIter = 15; // number of iterations | 15
// initialize constraints
// sphere collision constraint
CgSphereCollisionNode* sphereCollisionNode =
new CgSphereCollisionNode(g_system, g_clothMesh->vbuff(), radius, center);
// spring deformation constraint
CgSpringDeformationNode* deformationNode =
new CgSpringDeformationNode(g_system, g_clothMesh->vbuff(), tauc, deformIter);
deformationNode->addSprings(massSpringBuilder.getShearIndex());
deformationNode->addSprings(massSpringBuilder.getStructIndex());
// initialize user interaction
g_pickRenderer = new Renderer();
g_pickRenderer->setProgram(g_pickShader);
g_pickRenderer->setProgramInput(g_render_target);
g_pickRenderer->setElementCount(g_clothMesh->ibuffLen());
g_pickShader->setTessFact(SystemParam::n);
CgPointFixNode* mouseFixer = new CgPointFixNode(g_system, g_clothMesh->vbuff());
UI = new GridMeshUI(g_pickRenderer, mouseFixer, g_clothMesh->vbuff(), n);
// build constraint graph
g_cgRootNode = new CgRootNode(g_system, g_clothMesh->vbuff());
// first layer
g_cgRootNode->addChild(deformationNode);
g_cgRootNode->addChild(sphereCollisionNode);
// second layer
deformationNode->addChild(mouseFixer);
}
// G L U T C A L L B A C K S //////////////////////////////////////////////////////
static void display() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawCloth();
glutSwapBuffers();
checkGlErrors();
}
static void reshape(int w, int h) {
g_windowWidth = w;
g_windowHeight = h;
glViewport(0, 0, w, h);
updateProjection();
glutPostRedisplay();
}
static void mouse(const int button, const int state, const int x, const int y) {
g_mouseClickX = x;
g_mouseClickY = g_windowHeight - y - 1;
g_mouseLClickButton |= (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN);
g_mouseRClickButton |= (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN);
g_mouseMClickButton |= (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN);
g_mouseLClickButton &= !(button == GLUT_LEFT_BUTTON && state == GLUT_UP);
g_mouseRClickButton &= !(button == GLUT_RIGHT_BUTTON && state == GLUT_UP);
g_mouseMClickButton &= !(button == GLUT_MIDDLE_BUTTON && state == GLUT_UP);
g_mouseClickDown = g_mouseLClickButton || g_mouseRClickButton || g_mouseMClickButton;
// TODO: move to UserInteraction class: add renderer member variable
// pick point
if (g_mouseLClickButton) {
UI->setModelview(g_ModelViewMatrix);
UI->setProjection(g_ProjectionMatrix);
UI->grabPoint(g_mouseClickX, g_mouseClickY);
}
else UI->releasePoint();
}
static void motion(const int x, const int y) {
const float dx = float(x - g_mouseClickX);
const float dy = float (-(g_windowHeight - y - 1 - g_mouseClickY));
if (g_mouseLClickButton) {
//glm::vec3 ux(g_ModelViewMatrix * glm::vec4(1, 0, 0, 0));
//glm::vec3 uy(g_ModelViewMatrix * glm::vec4(0, 1, 0, 0));
glm::vec3 ux(0, 1, 0);
glm::vec3 uy(0, 0, -1);
UI->movePoint(0.01f * (dx * ux + dy * uy));
}
g_mouseClickX = x;
g_mouseClickY = g_windowHeight - y - 1;
}
// C L O T H ///////////////////////////////////////////////////////////////////////
static void drawCloth() {
Renderer renderer;
renderer.setProgram(g_phongShader);
renderer.setModelview(g_ModelViewMatrix);
renderer.setProjection(g_ProjectionMatrix);
g_phongShader->setAlbedo(g_albedo);
g_phongShader->setAmbient(g_ambient);
g_phongShader->setLight(g_light);
renderer.setProgramInput(g_render_target);
renderer.setElementCount(g_clothMesh->ibuffLen());
renderer.draw();
}
static void animateCloth(int value) {
// solve two time-steps
g_solver->solve(g_iter);
g_solver->solve(g_iter);
// fix points
CgSatisfyVisitor visitor;
visitor.satisfy(*g_cgRootNode);
// update normals
g_clothMesh->request_face_normals();
g_clothMesh->update_normals();
g_clothMesh->release_face_normals();
// update target
updateRenderTarget();
// redisplay
glutPostRedisplay();
// reset timer
glutTimerFunc(g_animation_timer, animateCloth, 0);
}
// S C E N E U P D A T E ///////////////////////////////////////////////////////////
static void updateProjection() {
g_ProjectionMatrix = glm::perspective(PI / 4.0f,
g_windowWidth * 1.0f / g_windowHeight, 0.01f, 1000.0f);
}
static void updateRenderTarget() {
// update vertex positions
g_render_target->setPositionData(g_clothMesh->vbuff(), g_clothMesh->vbuffLen());
// update vertex normals
g_render_target->setNormalData(g_clothMesh->nbuff(), g_clothMesh->vbuffLen());
}
// C L E A N U P //////////////////////////////////////////////////////////////////
static void cleanUp() {
// delete mesh
delete g_clothMesh;
// delete UI
delete g_pickRenderer;
delete UI;
// delete render target
delete g_render_target;
// delete mass-spring system
delete g_system;
delete g_solver;
// delete constraint graph
// TODO
}
// E R R O R S /////////////////////////////////////////////////////////////////////
void checkGlErrors() {
const GLenum errCode = glGetError();
if (errCode != GL_NO_ERROR) {
std::string error("GL Error: ");
error += reinterpret_cast<const char*>(gluErrorString(errCode));
std::cerr << error << std::endl;
throw std::runtime_error(error);
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="nupengl.core" version="0.1.0.1" targetFramework="native" />
<package id="nupengl.core.redist" version="0.1.0.1" targetFramework="native" />
</packages>

View File

@ -0,0 +1,19 @@
#version 430
uniform mat4 uModelViewMatrix;
// uniform mat4 uNormalMatrix;
uniform mat4 uProjectionMatrix;
layout(location=0) in vec3 aPosition;
layout(location=1) in vec3 aNormal;
layout(location=2) in vec2 aTexCoord;
out vec3 vNormal;
out vec2 vTexCoord;
void main(){
vNormal = aNormal;
vTexCoord = aTexCoord;
vec4 position = vec4(aPosition, 1.0);
gl_Position = uProjectionMatrix * uModelViewMatrix * position;
}

View File

@ -0,0 +1,22 @@
#version 430
in vec3 vNormal;
out vec4 fragColor;
uniform vec3 uAlbedo;
uniform vec3 uAmbient;
uniform vec3 uLight;
void main(){
vec3 normal = normalize(vNormal);
vec3 albedo = uAlbedo;
if(!gl_FrontFacing) {
normal = -normal;
albedo = 1.0 - albedo;
}
vec3 toLight = normalize(-uLight);
float diffuse = max(0, dot(toLight, normal));
vec3 color = diffuse * albedo + uAmbient * albedo;
fragColor = vec4(color, 1.0);
}

View File

@ -0,0 +1,12 @@
#version 430
in vec2 vTexCoord;
out vec4 fragColor;
uniform int uTessFact;
void main(){
float vx = round(vTexCoord.x * (uTessFact - 1));
float vy = round(vTexCoord.y * (uTessFact - 1));
fragColor = vec4(vx / (uTessFact - 1), vy / (uTessFact - 1), 0.2, 1.0);
}

33
thirdparty/FastMassSpring/FastMassSpring.sln vendored Executable file
View File

@ -0,0 +1,33 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ClothApp", "ClothApp\ClothApp.vcxproj", "{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D32F8712-F80F-46F9-85E1-FEB2BC718330}"
ProjectSection(SolutionItems) = preProject
readme.md = readme.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Debug|x64.ActiveCfg = Debug|x64
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Debug|x64.Build.0 = Debug|x64
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Debug|x86.ActiveCfg = Debug|Win32
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Debug|x86.Build.0 = Debug|Win32
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Release|x64.ActiveCfg = Release|x64
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Release|x64.Build.0 = Release|x64
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Release|x86.ActiveCfg = Release|Win32
{BBCA6691-6C35-4BFE-9CB9-13F16D29990F}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

21
thirdparty/FastMassSpring/LICENSE vendored Executable file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Samer Itani
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

16
thirdparty/FastMassSpring/readme.md vendored Executable file
View File

@ -0,0 +1,16 @@
A C++ implementation of *Fast Simulation of Mass-Spring Systems* [1], rendered with OpenGL.
The dynamic inverse procedure described in [2] was implemented to constrain spring deformations and prevent the "super-elastic" effect when using large time-steps.
### Dependencies
* **OpenGL, freeGLUT, GLEW, GLM** for rendering.
* **OpenMesh** for computing normals.
* **Eigen** for sparse matrix algebra.
### Demonstration
![curtain hang](https://media.giphy.com/media/5EC1drLIyBuHxRC4I8/giphy.gif)
![curtain drop](https://media.giphy.com/media/1zRbfGDmHTcn9RoUjy/giphy.gif)
### References
[1] Liu, T., Bargteil, A. W., Obrien, J. F., & Kavan, L. (2013). Fast simulation of mass-spring systems. *ACM Transactions on Graphics,32*(6), 1-7. doi:10.1145/2508363.2508406
[2] Provot, X. (1995). Deformation constraints in a mass-spring modelto describe rigid cloth behavior. *InGraphics Interface* 1995,147154.