From e1f0c16a9b888a1a2f78806e7c9d96abf581c143 Mon Sep 17 00:00:00 2001 From: Yuwei Xiao <xyw1105@126.com> Date: Tue, 24 Mar 2020 21:59:45 +0800 Subject: [PATCH] ex3 --- 3_cloth/CMakeLists.txt | 43 ++++++++++ 3_cloth/ClothSim.cpp | 100 ++++++++++++++++++++++ 3_cloth/ClothSim.h | 182 +++++++++++++++++++++++++++++++++++++++++ 3_cloth/main.cpp | 81 ++++++++++++++++++ CMakeLists.txt | 1 + 5 files changed, 407 insertions(+) create mode 100644 3_cloth/CMakeLists.txt create mode 100644 3_cloth/ClothSim.cpp create mode 100644 3_cloth/ClothSim.h create mode 100644 3_cloth/main.cpp diff --git a/3_cloth/CMakeLists.txt b/3_cloth/CMakeLists.txt new file mode 100644 index 0000000..d1a557c --- /dev/null +++ b/3_cloth/CMakeLists.txt @@ -0,0 +1,43 @@ + +cmake_minimum_required(VERSION 3.1) +project(3_cloth) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/../cmake) +set(CMAKE_CXX_FLAGS "-Wall") + +# libigl +option(LIBIGL_USE_STATIC_LIBRARY "Use libigl as static library" OFF) +option(LIBIGL_WITH_ANTTWEAKBAR "Use AntTweakBar" OFF) +option(LIBIGL_WITH_CGAL "Use CGAL" OFF) +option(LIBIGL_WITH_COMISO "Use CoMiso" OFF) +option(LIBIGL_WITH_CORK "Use Cork" OFF) +option(LIBIGL_WITH_EMBREE "Use Embree" OFF) +option(LIBIGL_WITH_LIM "Use LIM" OFF) +option(LIBIGL_WITH_MATLAB "Use Matlab" OFF) +option(LIBIGL_WITH_MOSEK "Use MOSEK" OFF) +option(LIBIGL_WITH_OPENGL "Use OpenGL" ON) +option(LIBIGL_WITH_OPENGL_GLFW "Use GLFW" ON) +option(LIBIGL_WITH_OPENGL_GLFW_IMGUI "Use ImGui" ON) +option(LIBIGL_WITH_PNG "Use PNG" OFF) +option(LIBIGL_WITH_PYTHON "Use Python" OFF) +option(LIBIGL_WITH_TETGEN "Use Tetgen" OFF) +option(LIBIGL_WITH_TRIANGLE "Use Triangle" OFF) +option(LIBIGL_WITH_VIEWER "Use OpenGL viewer" ON) +option(LIBIGL_WITH_XML "Use XML" OFF) + +if (NOT LIBIGL_FOUND) + find_package(LIBIGL REQUIRED QUIET) +endif() + +# Add default project files +file(GLOB LIBFILES ${PROJECT_SOURCE_DIR}/../include/*.*) +source_group("Library Files" FILES ${LIBFILES}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../include) + +# Add your project files +file(GLOB SRCFILES *.cpp) +file(GLOB HFILES *.h) + +add_definitions(-DIGL_VIEWER_VIEWER_QUIET) +add_executable(${PROJECT_NAME} ${SRCFILES} ${HFILES} ${LIBFILES} ) +target_link_libraries(${PROJECT_NAME} igl::core igl::opengl_glfw igl::opengl_glfw_imgui) \ No newline at end of file diff --git a/3_cloth/ClothSim.cpp b/3_cloth/ClothSim.cpp new file mode 100644 index 0000000..28bcf35 --- /dev/null +++ b/3_cloth/ClothSim.cpp @@ -0,0 +1,100 @@ +#include "ClothSim.h" +#include "igl/Timer.h" + +using Eigen::Vector2i; +typedef Eigen::Triplet<double> Triplet; +typedef Eigen::SparseMatrix<double> SparseMatrix; +typedef Eigen::Matrix<double, 3, 3> Matrix33; + +// add matrix into triplet array with index [i*3, i*3+1, i*3+2]*[j*3, j*3+1, j*3+2] +static void fillInTriplet(int i, int j, std::vector<Triplet>& triplets, const Matrix33& matrix) { + for(int a = 0; a < 3; ++a) + for(int b = 0; b < 3; ++b) + triplets.push_back(Triplet(i*3+a, j*3+b, matrix(a, b))); +} + +// add v into vector b in index [i*3, i*3+1, i*3+2] +static void addInVector(int i, Eigen::VectorXd& b, const Eigen::Vector3d& v) { + for(int a = 0; a < 3; ++a) + b[i*3+a] += v[a]; +} + +// To test if a sparse matrix A is symmetric +static bool isSymmetrical(Eigen::SparseMatrix<double>& A) { + bool ret = true; + Eigen::SparseMatrix<double> res = Eigen::SparseMatrix<double>(A.transpose()) - A; + for (unsigned int k = 0; k < res.outerSize(); k++) { + for (Eigen::SparseMatrix<double>::InnerIterator it(res, k); it; ++it) + { + if (std::fabs(it.value()) > 1e-6) { + std::cout<< "row:{} col:{} value: {}" << it.row() << it.col() << A.coeffRef(it.row(), it.col()); + return false; + } + } + } + return ret; +} + +// useful functions: +// 1. particleCoordinateToIdx() +// 2. isValidCoor() +bool ClothSim::advance() { + igl::Timer t; + t.start(); + if(m_method == 0) { // euler + + std::vector<Eigen::Vector3d> f; + f.resize(particles.size()); + + // TODO compute force + for(int idx = 0; idx < particles.size(); ++idx) { + + } + + // TODO constrain fixed particles + for(int i = 0; i < fixedParticleIdx.size(); ++i) { + + } + + // TODO update velocity and position of particles + for(int idx = 0; idx < particles.size(); ++idx) { + } + + } else if (m_method == 1) { // implicit euler + + std::vector<Triplet> triplets; + Eigen::VectorXd b(particles.size()*3); b.setZero(); + SparseMatrix A(particles.size()*3, particles.size()*3); + + // TODO compute triplets arrays and right-hand-size b + // NOTE: remember to process fixed particles, use isFixedParticle to test if a particle is fixed + for(int idx = 0; idx < particles.size(); ++idx) { + + } + + A.setFromTriplets(triplets.begin(), triplets.end()); + // NOTE: this is for debug purpose, make sure your A matrix is symmetric + // if(!isSymmetrical(A)) {std::cout<<"wrong"<<std::endl; return true;} + cgSolver.compute(A); + Eigen::VectorXd x = cgSolver.solve(b); + // std::cout<< "error:" << cgSolver.error() << ", iter:" << cgSolver.iterations() << std::endl; + + // TODO with x computed, update position and velocity of particles + for(int i = 0; i < particles.size(); ++i) { + + } + } else { // combination of a half step of explicit Euler and a half step of implicit Euler + // TODO + } + + // advance m_time + m_time += m_dt; + m_step++; + + // log + if ((m_step % m_log_frequency) == 0) { + std::cout<<t.getElapsedTimeInMicroSec()<<std::endl; + } + + return false; +} \ No newline at end of file diff --git a/3_cloth/ClothSim.h b/3_cloth/ClothSim.h new file mode 100644 index 0000000..fd93b58 --- /dev/null +++ b/3_cloth/ClothSim.h @@ -0,0 +1,182 @@ +#include "Simulation.h" +#include "igl/list_to_matrix.h" + +using namespace std; + +struct Spring { +public: + double damping; + double k_struct, k_bend, k_shear; + double L_struct, L_bend, L_shear; +}; + +struct Particle { +public: + Particle(const Eigen::Vector3d& p = Eigen::Vector3d::Zero(), const Eigen::Vector3d& v=Eigen::Vector3d::Zero()) + : position(p), velocity(v) {} + Eigen::Vector3d position; + Eigen::Vector3d velocity; +}; + +/* + */ +class ClothSim : public Simulation { +public: + ClothSim() : Simulation(), n(10), m(10), domainX(5) { + init(); + // m_trajectories.clear(); + // m_trajectoryColors.clear(); + } + + virtual void init() override { + std::string path = "sphere.off"; + m_objects.clear(); + m_objects.push_back(RigidObject(path)); + m_objects.push_back(RigidObject(path)); + p1 = &m_objects[0]; + p2 = &m_objects[1]; + + m_dt = 1e-2; + m_gravity << 0, -9.81, 0; + n = 10, m = 10; + domainX = 5; + dx = domainX / (n-1); + + cgSolver.setTolerance(1e-7); + cgSolver.setMaxIterations(1000); + reset(); + } + + virtual void resetMembers() override { + double domainY = dx * (m-1); + // initialize fixed point visualizations + p1->reset(); p2->reset(); + p1->setScale(0.01); p2->setScale(0.01); + p1->setPosition(Eigen::Vector3d(-domainX/2.0, domainY, 0)); + p2->setPosition(Eigen::Vector3d(domainX/2.0, domainY, 0)); + + // initialize particles + particles.resize(n * m); + for(int i = 0; i < n; ++i) { + for(int j = 0; j < m; ++j) { + particles[particleCoordinateToIdx(i, j)] = + Particle(Eigen::Vector3d(dx * i - domainX/2, domainY, dx * j), Eigen::Vector3d::Zero()); + } + } + + // initialize fixed particle + fixedParticleIdx.resize(2); + fixedParticleIdx[0] = particleCoordinateToIdx(0, 0); + fixedParticleIdx[1] = particleCoordinateToIdx(n-1, 0); + isFixedParticle.resize(n*m, false); + isFixedParticle[particleCoordinateToIdx(0, 0)] = true; + isFixedParticle[particleCoordinateToIdx(n-1, 0)] = true; + + // initialize cloth mesh triangles + std::vector<std::vector<int>> faces; + faces.reserve((n-1)*(m-1)*2); + for(int i = 0; i < n-1; ++i) { + for(int j = 0; j < m-1; ++j) { + faces.push_back({particleCoordinateToIdx(i, j), particleCoordinateToIdx(i, j+1), particleCoordinateToIdx(i+1, j+1)}); + faces.push_back({particleCoordinateToIdx(i, j), particleCoordinateToIdx(i+1, j+1), particleCoordinateToIdx(i+1, j)}); + } + } + igl::list_to_matrix(faces, m_cloth_renderF); + } + + virtual void updateRenderGeometry() override { + // create cloth geometry + for (size_t i = 0; i < m_objects.size(); i++) { + RigidObject &o = m_objects[i]; + if (o.getID() < 0) { // negative id means newly created object, reverse memory for it + m_renderVs.emplace_back(); + m_renderFs.emplace_back(); + } + + m_objects[i].getMesh(m_renderVs[i], m_renderFs[i]); + } + + m_cloth_renderV.resize(n*m, 3); + for(int i = 0; i < n*m; ++i) { + m_cloth_renderV.row(i) << particles[i].position.transpose(); + } + } + + virtual bool advance() override; + + virtual void renderRenderGeometry(igl::opengl::glfw::Viewer &viewer) override { + // render point + for (size_t i = 0; i < m_objects.size(); i++) { + RigidObject &o = m_objects[i]; + if (o.getID() < 0) { + int new_id = 0; + if (i > 0) { + new_id = viewer.append_mesh(); + o.setID(new_id); + } else { + o.setID(new_id); + } + + size_t meshIndex = viewer.mesh_index(o.getID()); + viewer.data_list[meshIndex].set_face_based(true); + viewer.data_list[meshIndex].point_size = 2.0f; + viewer.data_list[meshIndex].clear(); + } + size_t meshIndex = viewer.mesh_index(o.getID()); + + viewer.data_list[meshIndex].set_mesh(m_renderVs[i], m_renderFs[i]); + viewer.data_list[meshIndex].compute_normals(); + + Eigen::MatrixXd color; + o.getColors(color); + viewer.data_list[meshIndex].set_colors(color); + } + + if(cloth_render_id < 0) { + cloth_render_id = viewer.append_mesh(); + viewer.data_list[cloth_render_id].set_face_based(true); + viewer.data_list[cloth_render_id].point_size = 2.0f; + viewer.data_list[cloth_render_id].clear(); + } + viewer.data_list[cloth_render_id].set_mesh(m_cloth_renderV, m_cloth_renderF); + viewer.data_list[cloth_render_id].compute_normals(); + } + + void setMethod(int m) { m_method = m; } + void setSpring(Spring &s) { m_spring = s; } + void setLogFrequency(int f) { m_log_frequency = f; } + int n, m; + double m_mass; + double domainX, dx; + +private: + + int particleCoordinateToIdx(int i, int j) { return i*m + j; } + int particleCoordinateToIdx(const Eigen::Vector2i& coor) { return particleCoordinateToIdx(coor.x(), coor.y()); } + Eigen::Vector2i particleIdxToCoordinate(int idx) { return Eigen::Vector2i(int(idx/m), idx%m); } + bool isValidIdx(int idx) { return idx >= 0 && idx < particles.size(); } + bool isValidCoor(const Eigen::Vector2i& coor) { + return coor.x() >= 0 && coor.x() < n && coor.y() >= 0 && coor.y() < m; + } + + int m_method; // (0: euler, 1: implicit euler) + Eigen::Vector3d m_gravity; + Eigen::ConjugateGradient<Eigen::SparseMatrix<double>, Eigen::Lower|Eigen::Upper, Eigen::DiagonalPreconditioner<double>> cgSolver; + + Spring m_spring; + RigidObject *p1, *p2; + + std::vector<int> fixedParticleIdx; + std::vector<bool> isFixedParticle; + std::vector<Particle> particles; + std::vector<Eigen::MatrixXd> m_renderVs; // vertex positions for rendering + std::vector<Eigen::MatrixXi> m_renderFs; // face indices for rendering + int cloth_render_id = -1; + Eigen::MatrixXd m_cloth_renderV; // vertex positions for rendering + Eigen::MatrixXi m_cloth_renderF; // face indices for rendering + + int m_log_frequency; // how often should we log the COM in the GUI + // vector<vector<Eigen::Vector3d>> m_trajectories; + // Eigen::RowVector3d m_color, m_color1, m_color2; + // vector<Eigen::RowVector3d> m_trajectoryColors; +}; \ No newline at end of file diff --git a/3_cloth/main.cpp b/3_cloth/main.cpp new file mode 100644 index 0000000..ee587a5 --- /dev/null +++ b/3_cloth/main.cpp @@ -0,0 +1,81 @@ +#include <igl/writeOFF.h> +#include <thread> +#include "Gui.h" +#include "Simulator.h" +#include "ClothSim.h" + +/* + */ +class ClothGui : public Gui { + public: + // simulation parameters + float m_dt = 1e-2; + int n = 10, m = 10; + double mass = 1; + double domainX = 5; + int m_log_frequency = 30; + + Spring m_spring; // stores properties of the spring + ClothSim *p_clothSim = NULL; + const vector<char const *> m_integrators = {"Explicit Euler", "Implicit Euler", "Combination"}; + int m_selected_integrator = 0; + + + ClothGui() { + m_spring.damping = 10; + m_spring.k_struct = 3000; + m_spring.k_shear = 1000; + m_spring.k_bend = 500; + + // initialize the pendulum to be used + p_clothSim = new ClothSim(); + p_clothSim->setSpring(m_spring); + setSimulation(p_clothSim); + + start(); + } + + virtual void updateSimulationParameters() override { + // change all parameters of the simulation to the values that are set in the GUI + double dx = domainX / (n-1); + m_spring.L_struct = dx; + m_spring.L_shear = dx * sqrt(2); + m_spring.L_bend = 2 * dx; + + p_clothSim->setSpring(m_spring); + p_clothSim->setTimestep(m_dt); + p_clothSim->setLogFrequency(m_log_frequency); + p_clothSim->setMethod(m_selected_integrator); + p_clothSim->m_mass = mass; + p_clothSim->n = n; + p_clothSim->m = m; + p_clothSim->domainX = domainX; + p_clothSim->dx = dx; + } + + virtual void clearSimulation() override { + // + } + + + virtual void drawSimulationParameterMenu() override { + ImGui::InputInt("n", &n, 0, 0); + ImGui::InputInt("m", &m, 0, 0); + ImGui::InputDouble("mass", &mass, 0, 0); + ImGui::InputDouble("Domain X", &domainX, 0, 0); + ImGui::InputDouble("struct stiffness", &m_spring.k_struct, 0, 0); + ImGui::InputDouble("shear stiffness", &m_spring.k_shear, 0, 0); + ImGui::InputDouble("bend stiffness", &m_spring.k_bend, 0, 0); + ImGui::InputDouble("damping", &m_spring.damping, 0, 0); + ImGui::InputFloat("dt", &m_dt, 0, 0); + ImGui::Combo("Integrator", &m_selected_integrator, m_integrators.data(), m_integrators.size()); + ImGui::InputInt("Log Frequency", &m_log_frequency, 0, 0); + } +}; + +int main(int argc, char *argv[]) { + // create a new instance of the GUI for the pendulum simulation + new ClothGui(); + + return 0; +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c49e975..d67a197 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,3 +33,4 @@ endif() # add_subdirectory(1-2_spring) add_subdirectory(2-1_bead) add_subdirectory(2-2_pendulum) +add_subdirectory(3_cloth) \ No newline at end of file -- GitLab