diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html
index 96e1f1f9..8beecfcb 100644
--- a/ACKNOWLEDGEMENTS.html
+++ b/ACKNOWLEDGEMENTS.html
@@ -1271,4 +1271,29 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode
license to install, use, modify, prepare derivative works, incorporate into
other computer software, distribute, and sublicense such enhancements or
derivative works thereof, in binary and source code form.
+
+
+
FastMassSpring
+
+ 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.
\ No newline at end of file
diff --git a/dust3d.pro b/dust3d.pro
index 212b4457..5775e9ee 100644
--- a/dust3d.pro
+++ b/dust3d.pro
@@ -482,10 +482,20 @@ HEADERS += src/contourtopartconverter.h
SOURCES += src/remesher.cpp
HEADERS += src/remesher.h
+SOURCES += src/clothsimulator.cpp
+HEADERS += src/clothsimulator.h
+
+SOURCES += src/componentlayer.cpp
+HEADERS += src/componentlayer.h
+
SOURCES += src/main.cpp
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/instant-meshes-dust3d/src
INCLUDEPATH += thirdparty/instant-meshes/instant-meshes-dust3d/ext/tbb/include
diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts
index 95458196..be693a10 100644
--- a/languages/dust3d_zh_CN.ts
+++ b/languages/dust3d_zh_CN.ts
@@ -748,6 +748,10 @@ Tips:
面数
+
+
+ 层
+ PartWidget
@@ -1070,6 +1074,14 @@ Tips:
极高面
+
+
+ 身体
+
+
+
+ 衣服
+ RigWidget
diff --git a/src/clothsimulator.cpp b/src/clothsimulator.cpp
new file mode 100644
index 00000000..26b7aae1
--- /dev/null
+++ b/src/clothsimulator.cpp
@@ -0,0 +1,240 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "clothsimulator.h"
+#include "booleanmesh.h"
+
+typedef CGAL::Simple_cartesian K;
+typedef K::Point_3 Point;
+typedef K::Triangle_3 Triangle;
+typedef CGAL::Polyhedron_3 Polyhedron;
+typedef Polyhedron::HalfedgeDS HalfedgeDS;
+typedef CGAL::AABB_face_graph_triangle_primitive Primitive;
+typedef CGAL::AABB_traits Traits;
+typedef CGAL::AABB_tree Tree;
+typedef CGAL::Side_of_triangle_mesh Point_inside;
+
+template
+class Build_mesh : public CGAL::Modifier_base {
+public:
+ Build_mesh(const std::vector *vertices,
+ const std::vector> *faces) :
+ m_vertices(vertices),
+ m_faces(faces)
+ {
+ };
+ void operator()(HDS& hds)
+ {
+ // Postcondition: hds is a valid polyhedral surface.
+ CGAL::Polyhedron_incremental_builder_3 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 *m_vertices = nullptr;
+ const std::vector> *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 &collisionVertices,
+ const std::vector> &collisionTriangles) :
+ CgPointNode(system, vbuff)
+ {
+ if (!collisionTriangles.empty()) {
+ Build_mesh 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 &vertices,
+ const std::vector> &faces,
+ const std::vector &collisionVertices,
+ const std::vector> &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 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> 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 *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 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);
+}
diff --git a/src/clothsimulator.h b/src/clothsimulator.h
new file mode 100644
index 00000000..6c238fb4
--- /dev/null
+++ b/src/clothsimulator.h
@@ -0,0 +1,41 @@
+#ifndef DUST3D_CLOTH_SIMULATOR_H
+#define DUST3D_CLOTH_SIMULATOR_H
+#include
+#include
+#include
+
+struct mass_spring_system;
+class MassSpringSolver;
+class CgRootNode;
+class CgSpringDeformationNode;
+class CgMeshCollisionNode;
+
+class ClothSimulator : public QObject
+{
+ Q_OBJECT
+public:
+ ClothSimulator(const std::vector &vertices,
+ const std::vector> &faces,
+ const std::vector &collisionVertices,
+ const std::vector> &collisionTriangles);
+ ~ClothSimulator();
+ void create();
+ void step();
+ void getCurrentVertices(std::vector *currentVertices);
+private:
+ std::vector m_vertices;
+ std::vector> m_faces;
+ std::vector m_collisionVertices;
+ std::vector> m_collisionTriangles;
+ std::vector m_clothPointBuffer;
+ std::vector m_clothPointSources;
+ std::vector> 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
diff --git a/src/componentlayer.cpp b/src/componentlayer.cpp
new file mode 100644
index 00000000..603c0929
--- /dev/null
+++ b/src/componentlayer.cpp
@@ -0,0 +1,6 @@
+#include
+#include "componentlayer.h"
+
+IMPL_ComponentLayerToString
+IMPL_ComponentLayerFromString
+IMPL_ComponentLayerToDispName
\ No newline at end of file
diff --git a/src/componentlayer.h b/src/componentlayer.h
new file mode 100644
index 00000000..0202a819
--- /dev/null
+++ b/src/componentlayer.h
@@ -0,0 +1,49 @@
+#ifndef DUST3D_COMPONENT_LAYER_H
+#define DUST3D_COMPONENT_LAYER_H
+#include
+
+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
diff --git a/src/document.cpp b/src/document.cpp
index dd0e6ae5..6837e389 100644
--- a/src/document.cpp
+++ b/src/document.cpp
@@ -1201,6 +1201,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId
component["smoothSeam"] = QString::number(componentIt.second.smoothSeam);
if (componentIt.second.polyCount != PolyCount::Original)
component["polyCount"] = PolyCountToString(componentIt.second.polyCount);
+ if (componentIt.second.layer != ComponentLayer::Body)
+ component["layer"] = ComponentLayerToString(componentIt.second.layer);
QStringList childIdList;
for (const auto &childId: componentIt.second.childrenIds) {
childIdList.append(childId.toString());
@@ -1708,6 +1710,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
if (smoothSeamIt != componentKv.second.end())
component.setSmoothSeam(smoothSeamIt->second.toFloat());
component.polyCount = PolyCountFromString(valueOfKeyInMapOrEmpty(componentKv.second, "polyCount").toUtf8().constData());
+ component.layer = ComponentLayerFromString(valueOfKeyInMapOrEmpty(componentKv.second, "layer").toUtf8().constData());
//qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name;
if ("partId" == linkDataType) {
QUuid partId = oldNewIdMap[QUuid(linkData)];
@@ -2497,6 +2500,20 @@ void Document::setComponentPolyCount(QUuid componentId, PolyCount count)
emit skeletonChanged();
}
+void Document::setComponentLayer(QUuid componentId, ComponentLayer layer)
+{
+ Component *component = (Component *)findComponent(componentId);
+ if (nullptr == component)
+ return;
+ if (component->layer == layer)
+ return;
+
+ component->layer = layer;
+ component->dirty = true;
+ emit componentLayerChanged(componentId);
+ emit skeletonChanged();
+}
+
void Document::createNewComponentAndMoveThisIn(QUuid componentId)
{
auto component = componentMap.find(componentId);
diff --git a/src/document.h b/src/document.h
index 9b4f62c6..7a40a94f 100644
--- a/src/document.h
+++ b/src/document.h
@@ -30,6 +30,7 @@
#include "preferences.h"
#include "paintmode.h"
#include "proceduralanimation.h"
+#include "componentlayer.h"
class MaterialPreviewsGenerator;
class MotionsGenerator;
@@ -67,6 +68,7 @@ public:
float smoothAll = 0.0;
float smoothSeam = 0.0;
PolyCount polyCount = PolyCount::Original;
+ ComponentLayer layer = ComponentLayer::Body;
std::vector childrenIds;
QString linkData() const
{
@@ -396,6 +398,7 @@ signals:
void componentSmoothAllChanged(QUuid componentId);
void componentSmoothSeamChanged(QUuid componentId);
void componentPolyCountChanged(QUuid componentId);
+ void componentLayerChanged(QUuid componentId);
void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId);
@@ -642,6 +645,7 @@ public slots:
void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
void setComponentPolyCount(QUuid componentId, PolyCount count);
+ void setComponentLayer(QUuid componentId, ComponentLayer layer);
void hideOtherComponents(QUuid componentId);
void lockOtherComponents(QUuid componentId);
void hideAllComponents();
diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp
index cf62e4c6..bbc3f31d 100644
--- a/src/documentwindow.cpp
+++ b/src/documentwindow.cpp
@@ -1014,6 +1014,7 @@ DocumentWindow::DocumentWindow() :
connect(partTreeWidget, &PartTreeWidget::setComponentSmoothAll, m_document, &Document::setComponentSmoothAll);
connect(partTreeWidget, &PartTreeWidget::setComponentSmoothSeam, m_document, &Document::setComponentSmoothSeam);
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::removeComponent, m_document, &Document::removeComponent);
connect(partTreeWidget, &PartTreeWidget::hideOtherComponents, m_document, &Document::hideOtherComponents);
diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp
index a4233a5e..c5a24741 100644
--- a/src/meshgenerator.cpp
+++ b/src/meshgenerator.cpp
@@ -18,6 +18,7 @@
#include "triangulatefaces.h"
#include "remesher.h"
#include "polycount.h"
+#include "clothsimulator.h"
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
m_snapshot(snapshot)
@@ -840,6 +841,11 @@ CombineMode MeshGenerator::componentCombineMode(const std::map
combineMode = CombineMode::Inversion;
if (componentRemeshed(component))
combineMode = CombineMode::Uncombined;
+ if (combineMode == CombineMode::Normal) {
+ if (ComponentLayer::Body != ComponentLayerFromString(valueOfKeyInMapOrEmpty(*component, "layer").toUtf8().constData())) {
+ combineMode = CombineMode::Uncombined;
+ }
+ }
}
return combineMode;
}
@@ -879,6 +885,13 @@ QString MeshGenerator::componentColorName(const std::map *comp
return QString();
}
+ComponentLayer MeshGenerator::componentLayer(const std::map *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 *mesh = nullptr;
@@ -1035,6 +1048,16 @@ MeshCombiner::Mesh *MeshGenerator::combineComponentMesh(const QString &component
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) {
float polyCountValue = 1.0f;
bool remeshed = componentId.isNull() ? componentRemeshed(&m_snapshot->canvas, &polyCountValue) : componentRemeshed(component, &polyCountValue);
@@ -1386,6 +1409,7 @@ void MeshGenerator::generate()
// Recursively check uncombined components
collectUncombinedComponent(QUuid().toString());
+ collectClothComponent(QUuid().toString());
// Collect errored parts
for (const auto &it: m_cacheContext->parts) {
@@ -1514,6 +1538,8 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
{
const auto &component = findComponent(componentIdString);
if (CombineMode::Uncombined == componentCombineMode(component)) {
+ if (ComponentLayer::Body != componentLayer(component))
+ return;
const auto &componentCache = m_cacheContext->components[componentIdString];
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
qDebug() << "Uncombined mesh is null";
@@ -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 uncombinedVertices;
+ std::vector> uncombinedFaces;
+ componentCache.mesh->fetch(uncombinedVertices, uncombinedFaces);
+
+ std::map> positionMap;
+ for (const auto &it: componentCache.outcomeNodeVertices) {
+ positionMap.insert({PositionKey(it.first), it.second});
+ }
+ std::vector> 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> &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 &vertices, const std::vector> &triangles,
const std::vector &triangleNormals,
std::vector> *triangleVertexNormals)
diff --git a/src/meshgenerator.h b/src/meshgenerator.h
index 8496980a..882f966d 100644
--- a/src/meshgenerator.h
+++ b/src/meshgenerator.h
@@ -11,6 +11,7 @@
#include "snapshot.h"
#include "combinemode.h"
#include "meshloader.h"
+#include "componentlayer.h"
class GeneratedPart
{
@@ -100,6 +101,8 @@ private:
std::map *m_cutFaceTransforms = nullptr;
std::map> *m_nodesCutFaces = nullptr;
quint64 m_id = 0;
+ std::vector m_clothCollisionVertices;
+ std::vector> m_clothCollisionTriangles;
void collectParts();
bool checkIsComponentDirty(const QString &componentIdString);
@@ -125,7 +128,9 @@ private:
GeneratedComponent &componentCache);
MeshCombiner::Mesh *combineMultipleMeshes(const std::vector> &multipleMeshes, bool recombine=true);
QString componentColorName(const std::map *component);
+ ComponentLayer componentLayer(const std::map *component);
void collectUncombinedComponent(const QString &componentIdString);
+ void collectClothComponent(const QString &componentIdString);
void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector &cutTemplate);
void remesh(const std::vector &inputNodes,
const std::vector &inputVertices,
diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp
index 612574c6..a61c69bd 100644
--- a/src/parttreewidget.cpp
+++ b/src/parttreewidget.cpp
@@ -315,6 +315,17 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
}
QWidget *widget = new QWidget;
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(&QComboBox::currentIndexChanged), this, [=](int index) {
+ emit setComponentLayer(component->id, (ComponentLayer)index);
+ emit groupOperationAdded();
+ });
+
QComboBox *combineModeSelectBox = new QComboBox;
for (size_t i = 0; i < (size_t)CombineMode::Count; ++i) {
CombineMode mode = (CombineMode)i;
@@ -368,6 +379,7 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
if (nullptr != partTargetSelectBox)
componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox);
componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox);
+ componentSettingsLayout->addRow(tr("Layer"), componentLayerSelectBox);
QVBoxLayout *newLayout = new QVBoxLayout;
newLayout->addLayout(layout);
diff --git a/src/parttreewidget.h b/src/parttreewidget.h
index a8bb21c8..72aef4b9 100644
--- a/src/parttreewidget.h
+++ b/src/parttreewidget.h
@@ -23,6 +23,7 @@ signals:
void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
void setComponentPolyCount(QUuid componentId, PolyCount count);
+ void setComponentLayer(QUuid componentId, ComponentLayer layer);
void setPartTarget(QUuid partId, PartTarget target);
void setPartBase(QUuid partId, PartBase base);
void moveComponent(QUuid componentId, QUuid toParentId);
diff --git a/thirdparty/FastMassSpring/.gitattributes b/thirdparty/FastMassSpring/.gitattributes
new file mode 100755
index 00000000..1ff0c423
--- /dev/null
+++ b/thirdparty/FastMassSpring/.gitattributes
@@ -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
diff --git a/thirdparty/FastMassSpring/.gitignore b/thirdparty/FastMassSpring/.gitignore
new file mode 100755
index 00000000..1c9a181a
--- /dev/null
+++ b/thirdparty/FastMassSpring/.gitignore
@@ -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/
diff --git a/thirdparty/FastMassSpring/ClothApp/ClothApp.vcxproj b/thirdparty/FastMassSpring/ClothApp/ClothApp.vcxproj
new file mode 100755
index 00000000..bdf49dac
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/ClothApp.vcxproj
@@ -0,0 +1,204 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {bbca6691-6c35-4bfe-9cb9-13f16d29990f}
+ Win32Proj
+ ClothApp
+ 8.1
+
+
+
+ Application
+ true
+ v140
+ Unicode
+
+
+ Application
+ false
+ v140
+ true
+ Unicode
+
+
+ Application
+ true
+ v140
+ Unicode
+
+
+ Application
+ false
+ v140
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)packages\nupengl.core.0.1.0.1\build\native\include;$(IncludePath)
+ static
+ static
+
+
+ true
+ C:\Program Files\Eigen\include;C:\Program Files\OpenMesh 7.1\include;$(IncludePath)
+ C:\Program Files\OpenMesh 7.1\lib;$(LibraryPath)
+
+
+ false
+ $(SolutionDir)packages\nupengl.core.0.1.0.1\build\native\include;$(IncludePath)
+
+
+ false
+ C:\Program Files\Eigen\include;C:\Program Files\OpenMesh 7.1\include;$(IncludePath)
+ C:\Program Files\OpenMesh 7.1\lib;$(LibraryPath)
+
+
+
+
+
+ Level3
+ Disabled
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ %(AdditionalLibraryDirectories)
+ %(AdditionalDependencies)
+
+
+
+
+
+
+ Level3
+ Disabled
+ _USE_MATH_DEFINES;_SCL_SECURE_NO_WARNINGS
+ true
+
+
+ Console
+ true
+ OpenMeshCored.lib;OpenMeshToolsd.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+
+
+ MaxSpeed
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+
+
+ MaxSpeed
+ true
+ true
+ _USE_MATH_DEFINES;_SCL_SECURE_NO_WARNINGS;NDEBUG
+ true
+
+
+ Console
+ true
+ true
+ true
+ OpenMeshCore.lib;OpenMeshTools.lib;%(AdditionalDependencies)
+
+
+
+
+
+ true
+ true
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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}.
+
+
+
+
+
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/ClothApp.vcxproj.filters b/thirdparty/FastMassSpring/ClothApp/ClothApp.vcxproj.filters
new file mode 100755
index 00000000..3b0af28a
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/ClothApp.vcxproj.filters
@@ -0,0 +1,71 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {55e50294-8ec8-4842-bc08-005ce41662d4}
+
+
+
+
+
+ Shader Files
+
+
+ Shader Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Shader Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/MassSpringSolver.cpp b/thirdparty/FastMassSpring/ClothApp/MassSpringSolver.cpp
new file mode 100755
index 00000000..83e314a0
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/MassSpringSolver.cpp
@@ -0,0 +1,384 @@
+#include "MassSpringSolver.h"
+#include
+
+// 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 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); }
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/MassSpringSolver.h b/thirdparty/FastMassSpring/ClothApp/MassSpringSolver.h
new file mode 100755
index 00000000..d01a604e
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/MassSpringSolver.h
@@ -0,0 +1,230 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+
+// Mass-Spring System struct
+struct mass_spring_system {
+ typedef Eigen::SparseMatrix SparseMatrix;
+ typedef Eigen::VectorXf VectorXf;
+ typedef std::pair Edge;
+ typedef std::vector 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 SparseMatrix;
+ typedef Eigen::SimplicialLLT > Cholesky;
+ typedef Eigen::Map Map;
+ typedef std::pair Edge;
+ typedef Eigen::Triplet Triplet;
+ typedef std::vector 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 Edge;
+ typedef std::vector EdgeList;
+ typedef Eigen::Triplet Triplet;
+ typedef std::vector TripletList;
+ typedef std::vector 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 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 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 Edge;
+ typedef Eigen::Vector3f Vector3f;
+ std::unordered_set 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 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);
+};
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/Mesh.cpp b/thirdparty/FastMassSpring/ClothApp/Mesh.cpp
new file mode 100755
index 00000000..14176238
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/Mesh.cpp
@@ -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& _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 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 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; }
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/Mesh.h b/thirdparty/FastMassSpring/ClothApp/Mesh.h
new file mode 100755
index 00000000..dff0874b
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/Mesh.h
@@ -0,0 +1,42 @@
+#pragma once
+#include
+#include
+
+// 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 _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& _ibuff);
+};
+
+class MeshBuilder {
+private:
+ Mesh* result;
+
+public:
+ void uniformGrid(float w, int n);
+ Mesh* getResult();
+};
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/Renderer.cpp b/thirdparty/FastMassSpring/ClothApp/Renderer.cpp
new file mode 100755
index 00000000..e8f0f46e
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/Renderer.cpp
@@ -0,0 +1,49 @@
+#include "Renderer.h"
+#include
+
+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);
+}
+
diff --git a/thirdparty/FastMassSpring/ClothApp/Renderer.h b/thirdparty/FastMassSpring/ClothApp/Renderer.h
new file mode 100755
index 00000000..525184f7
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/Renderer.h
@@ -0,0 +1,22 @@
+#pragma once
+#include
+#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();
+};
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/Shader.cpp b/thirdparty/FastMassSpring/ClothApp/Shader.cpp
new file mode 100755
index 00000000..2eb37221
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/Shader.cpp
@@ -0,0 +1,190 @@
+#include "Shader.h"
+#include
+#include
+#include
+
+// 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 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 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);
+}
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/Shader.h b/thirdparty/FastMassSpring/ClothApp/Shader.h
new file mode 100755
index 00000000..ee86347a
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/Shader.h
@@ -0,0 +1,87 @@
+#pragma once
+#include
+#include
+#include
+
+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);
+};
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/UserInteraction.cpp b/thirdparty/FastMassSpring/ClothApp/UserInteraction.cpp
new file mode 100755
index 00000000..c66e2415
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/UserInteraction.cpp
@@ -0,0 +1,48 @@
+#include "UserInteraction.h"
+#include
+#include
+#include
+
+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;
+}
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/UserInteraction.h b/thirdparty/FastMassSpring/ClothApp/UserInteraction.h
new file mode 100755
index 00000000..93ae39e3
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/UserInteraction.h
@@ -0,0 +1,35 @@
+#pragma once
+#include
+
+#include "MassSpringSolver.h"
+#include "Renderer.h"
+
+class UserInteraction {
+protected:
+ typedef glm::vec3 vec3;
+ typedef std::vector 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);
+};
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/app.cpp b/thirdparty/FastMassSpring/ClothApp/app.cpp
new file mode 100755
index 00000000..6de82bdf
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/app.cpp
@@ -0,0 +1,471 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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();
+
+// 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(gluErrorString(errCode));
+ std::cerr << error << std::endl;
+ throw std::runtime_error(error);
+ }
+}
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/packages.config b/thirdparty/FastMassSpring/ClothApp/packages.config
new file mode 100755
index 00000000..5b03888f
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/shaders/basic.vshader b/thirdparty/FastMassSpring/ClothApp/shaders/basic.vshader
new file mode 100755
index 00000000..b6128bf6
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/shaders/basic.vshader
@@ -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;
+}
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/shaders/phong.fshader b/thirdparty/FastMassSpring/ClothApp/shaders/phong.fshader
new file mode 100755
index 00000000..c473e871
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/shaders/phong.fshader
@@ -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);
+}
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/ClothApp/shaders/pick.fshader b/thirdparty/FastMassSpring/ClothApp/shaders/pick.fshader
new file mode 100755
index 00000000..9e21c5b0
--- /dev/null
+++ b/thirdparty/FastMassSpring/ClothApp/shaders/pick.fshader
@@ -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);
+}
\ No newline at end of file
diff --git a/thirdparty/FastMassSpring/FastMassSpring.sln b/thirdparty/FastMassSpring/FastMassSpring.sln
new file mode 100755
index 00000000..1c4edec1
--- /dev/null
+++ b/thirdparty/FastMassSpring/FastMassSpring.sln
@@ -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
diff --git a/thirdparty/FastMassSpring/LICENSE b/thirdparty/FastMassSpring/LICENSE
new file mode 100755
index 00000000..2c1edebf
--- /dev/null
+++ b/thirdparty/FastMassSpring/LICENSE
@@ -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.
diff --git a/thirdparty/FastMassSpring/readme.md b/thirdparty/FastMassSpring/readme.md
new file mode 100755
index 00000000..cd32ba53
--- /dev/null
+++ b/thirdparty/FastMassSpring/readme.md
@@ -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.
\ No newline at end of file