From cf071f8b32bc0ff39190d4ef48d021e96e890062 Mon Sep 17 00:00:00 2001 From: Yuwei Xiao <xyw1105@126.com> Date: Wed, 11 Mar 2020 00:25:17 +0800 Subject: [PATCH] ex1 update --- 1-0_earth/CMakeLists.txt | 43 +++++++++ 1-0_earth/EarthSim.h | 93 +++++++++++++++++++ 1-0_earth/main.cpp | 41 ++++++++ 1-1_cannonball/CMakeLists.txt | 43 +++++++++ 1-1_cannonball/CannonBallSim.cpp | 45 +++++++++ 1-1_cannonball/CannonBallSim.h | 151 ++++++++++++++++++++++++++++++ 1-1_cannonball/main.cpp | 96 +++++++++++++++++++ 1-2_spring/CMakeLists.txt | 43 +++++++++ 1-2_spring/SpringSim.cpp | 50 ++++++++++ 1-2_spring/SpringSim.h | 154 +++++++++++++++++++++++++++++++ 1-2_spring/main.cpp | 123 ++++++++++++++++++++++++ CMakeLists.txt | 5 +- include/Simulation.h | 1 + 13 files changed, 887 insertions(+), 1 deletion(-) create mode 100644 1-0_earth/CMakeLists.txt create mode 100644 1-0_earth/EarthSim.h create mode 100644 1-0_earth/main.cpp create mode 100644 1-1_cannonball/CMakeLists.txt create mode 100644 1-1_cannonball/CannonBallSim.cpp create mode 100644 1-1_cannonball/CannonBallSim.h create mode 100644 1-1_cannonball/main.cpp create mode 100644 1-2_spring/CMakeLists.txt create mode 100644 1-2_spring/SpringSim.cpp create mode 100644 1-2_spring/SpringSim.h create mode 100644 1-2_spring/main.cpp diff --git a/1-0_earth/CMakeLists.txt b/1-0_earth/CMakeLists.txt new file mode 100644 index 0000000..da74f69 --- /dev/null +++ b/1-0_earth/CMakeLists.txt @@ -0,0 +1,43 @@ + +cmake_minimum_required(VERSION 3.1) +project(1_earth) + +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/1-0_earth/EarthSim.h b/1-0_earth/EarthSim.h new file mode 100644 index 0000000..f02088e --- /dev/null +++ b/1-0_earth/EarthSim.h @@ -0,0 +1,93 @@ +#include "Simulation.h" + +using namespace std; + + +class EarthSim : public Simulation { +public: + EarthSim() : Simulation() { + init(); + } + + virtual void init() override { + std::string path = "sphere.off"; + m_objects.clear(); + m_objects.push_back(RigidObject(path)); + m_objects.push_back(RigidObject(path)); + p_earth = &m_objects[0]; + p_moon = &m_objects[1]; + + m_dt = 5e-2; + m_radius = 10; + + reset(); + } + + virtual void resetMembers() override { + p_earth->reset(); + p_earth->setScale(0.3); + p_earth->setPosition(Eigen::Vector3d(0, 0, 0)); + + p_moon->reset(); + p_moon->setScale(0.1); + p_moon->setPosition(Eigen::Vector3d(cos(getTime()), 0, sin(getTime())) * m_radius); + } + + virtual void updateRenderGeometry() override { + 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]); + } + } + + virtual bool advance() override { + // TODO update p_moon + + // update m_time + // update m_step + + return false; + } + + virtual void renderRenderGeometry(igl::opengl::glfw::Viewer &viewer) override { + 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); + } + } + + void setRadius(float r) { m_radius = r; } + +private: + RigidObject *p_earth, *p_moon; + float m_radius; + + std::vector<Eigen::MatrixXd> m_renderVs; // vertex positions for rendering + std::vector<Eigen::MatrixXi> m_renderFs; // face indices for rendering +}; \ No newline at end of file diff --git a/1-0_earth/main.cpp b/1-0_earth/main.cpp new file mode 100644 index 0000000..c6a1c1b --- /dev/null +++ b/1-0_earth/main.cpp @@ -0,0 +1,41 @@ +#include "EarthSim.h" +#include "Gui.h" + +/* + * GUI for the earth simulation. + */ +class EarthGui : public Gui { + public: + // simulation parameters + float m_dt = 1e-2; + float m_radius = 10.0; + + EarthSim *p_EarthSim = NULL; + + EarthGui() { + // create a new Earth simulation, set it in the GUI, and start the GUI + p_EarthSim = new EarthSim(); + setSimulation(p_EarthSim); + + start(); + } + + virtual void updateSimulationParameters() override { + // change all parameters of the simulation to the values that are set in the GUI + p_EarthSim->setTimestep(m_dt); + p_EarthSim->setRadius(m_radius); + } + + + virtual void drawSimulationParameterMenu() override { + ImGui::InputFloat("Radius", &m_radius, 0, 0); + ImGui::InputFloat("dt", &m_dt, 0, 0); + } +}; + +int main(int argc, char *argv[]) { + // create a new instance of the GUI for the Earth simulation + new EarthGui(); + + return 0; +} \ No newline at end of file diff --git a/1-1_cannonball/CMakeLists.txt b/1-1_cannonball/CMakeLists.txt new file mode 100644 index 0000000..d91cc47 --- /dev/null +++ b/1-1_cannonball/CMakeLists.txt @@ -0,0 +1,43 @@ + +cmake_minimum_required(VERSION 3.1) +project(1-1_cannonball) + +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/1-1_cannonball/CannonBallSim.cpp b/1-1_cannonball/CannonBallSim.cpp new file mode 100644 index 0000000..0eafcfa --- /dev/null +++ b/1-1_cannonball/CannonBallSim.cpp @@ -0,0 +1,45 @@ +#include "CannonBallSim.h" + + +bool CannonBallSim::advance() { + // perform time integration with different integrators + + // use p_ball, m_dt, m_gravity + Eigen::Vector3d v = p_ball->getLinearVelocity(); + Eigen::Vector3d p = p_ball->getPosition(); + + // TODO + switch (m_method) { + case 0: + // analytical solution + // v(t) = v_0*t + 0.5*a*t^2 + break; + + case 1: + // explicit euler + // p' = p + dt*v + // v' = v + dt*a + break; + + case 2: + // symplectic euler + // v' = v + dt*a + // p' = p + dt*v' + break; + + default: + std::cerr << m_method << " is not a valid integrator method." + << std::endl; + } + + // advance time + m_time += m_dt; + m_step++; + + // log + if ((m_step % m_log_frequency) == 0) { + m_trajectories.back().push_back(p_ball->getPosition()); + } + + return false; +} \ No newline at end of file diff --git a/1-1_cannonball/CannonBallSim.h b/1-1_cannonball/CannonBallSim.h new file mode 100644 index 0000000..4584f97 --- /dev/null +++ b/1-1_cannonball/CannonBallSim.h @@ -0,0 +1,151 @@ +#include "Simulation.h" + +using namespace std; + +/* + * Simulation that shoots a sphere in a given direction with a given force using + * several kinds of integrators. + */ +class CannonBallSim : public Simulation { +public: + CannonBallSim() : Simulation() { + 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)); + p_ball = &m_objects.back(); + + m_dt = 5e-2; + m_mass = 1.0; + m_log_frequency = 5; + m_method = 0; + m_gravity << 0, -9.81, 0; + + reset(); + } + + virtual void resetMembers() override { + p_ball->reset(); + p_ball->setScale(0.1); + p_ball->setPosition(Eigen::Vector3d(0, 0, 0)); + p_ball->setMass(m_mass); + updateVars(); + + if (m_trajectories.size() == 0 || m_trajectories.back().size() > 1) { + m_trajectories.push_back(vector<Eigen::Vector3d>()); + m_trajectoryColors.push_back(m_color); + } else { + m_trajectoryColors.back() = m_color; + } + + setMethod(m_method); + } + + virtual void updateRenderGeometry() override { + p_ball->getMesh(m_renderV, m_renderF); + } + + virtual bool advance() override; + + virtual void renderRenderGeometry( + igl::opengl::glfw::Viewer &viewer) override { + viewer.data().set_mesh(m_renderV, m_renderF); + + for (size_t trajectory = 0; trajectory < m_trajectories.size(); + trajectory++) { + for (size_t point = 0; point < m_trajectories[trajectory].size(); + point++) { + viewer.data().add_points( + m_trajectories[trajectory][point].transpose(), + m_trajectoryColors[trajectory]); + } + } + } + + void clearTrajectories() { + m_trajectories.clear(); + m_trajectories.push_back(vector<Eigen::Vector3d>()); + m_trajectoryColors.clear(); + m_trajectoryColors.push_back(m_color); + } + +#pragma region SettersAndGetters + /* + * Compute magnitude and direction of momentum and apply it to ball + */ + void updateVars() { + Eigen::Vector3d momentum; + momentum << std::cos(m_angle), std::sin(m_angle), 0; + momentum *= m_force; + p_ball->setLinearVelocity(momentum / p_ball->getMass()); + } + + void setAngle(double a) { + m_angle = a; + updateVars(); + } + + void setForce(double f) { + m_force = f; + updateVars(); + } + + void setMass(double m) { m_mass = m; } + + void setMethod(int m) { + m_method = m; + switch (m_method) { + case 0: + m_color = Eigen::RowVector3d(1.0, 0.0, 0.0); + break; + case 1: + m_color = Eigen::RowVector3d(0.0, 1.0, 0.0); + break; + case 2: + m_color = Eigen::RowVector3d(0.0, 0.0, 1.0); + break; + default: + std::cerr << m_method << " is not a valid integrator method." + << std::endl; + } + if (m_step == 0) { + m_trajectoryColors.back() = m_color; + } + } + + void setLogFrequency(int f) { m_log_frequency = f; } + + void getTrajectories(int index, Eigen::MatrixX3d &mat) const { + int num_points = m_trajectories[index].size(); + mat.resize(num_points, 3); + for (int i = 0; i < num_points; i++) { + mat.row(i) = m_trajectories[index][i]; + } + } + + int getNumTrajectories() const { return m_trajectories.size(); } +#pragma endregion SettersAndGetters + +private: + int m_method; // id of integrator to be used (0: analytical, 1: explicit euler, 2: semi-implicit euler) + double m_angle; + double m_force; + double m_mass; + + Eigen::Vector3d m_gravity; + + RigidObject *p_ball; + + Eigen::MatrixXd m_renderV; // vertex positions for rendering + Eigen::MatrixXi m_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; + vector<Eigen::RowVector3d> m_trajectoryColors; +}; \ No newline at end of file diff --git a/1-1_cannonball/main.cpp b/1-1_cannonball/main.cpp new file mode 100644 index 0000000..eeba893 --- /dev/null +++ b/1-1_cannonball/main.cpp @@ -0,0 +1,96 @@ +#include <igl/writeOFF.h> +#include "CannonBallSim.h" +#include "Gui.h" + +/* + * GUI for the cannonball simulation. This time we need additional paramters, + * e.g. which integrator to use for the simulation and the force applied to the + * cannonball, and we also add some more visualizations (trajectories). + */ +class CannonBallGui : public Gui { + public: + // simulation parameters + float m_angle = 1.047f; + float m_force = 30.0f; + float m_dt = 1e-2; + float m_mass = 1.0; + int m_log_frequency = 30; + + CannonBallSim *p_cannonBallSim = NULL; + + const vector<char const *> m_integrators = {"Analytic", "Explicit Euler", + "Symplectic Euler"}; + int m_selected_integrator = 0; + + CannonBallGui() { + // create a new cannonball simulation, set it in the GUI, + // and start the GUI + p_cannonBallSim = new CannonBallSim(); + setSimulation(p_cannonBallSim); + + // show vertex velocity instead of normal + callback_clicked_vertex = [&](int clickedVertexIndex, + int clickedObjectIndex, + Eigen::Vector3d &pos, + Eigen::Vector3d &dir) { + RigidObject &o = p_cannonBallSim->getObjects()[clickedObjectIndex]; + pos = o.getVertexPosition(clickedVertexIndex); + dir = o.getVelocity(pos); + }; + start(); + } + + virtual void updateSimulationParameters() override { + // change all parameters of the simulation to the values that are set in + // the GUI + p_cannonBallSim->setForce(m_force); + p_cannonBallSim->setAngle(m_angle); + p_cannonBallSim->setTimestep(m_dt); + p_cannonBallSim->setMass(m_mass); + p_cannonBallSim->setMethod(m_selected_integrator); + p_cannonBallSim->setLogFrequency(m_log_frequency); + } + + virtual void clearSimulation() override { + p_cannonBallSim->clearTrajectories(); + } + + /* + * Writes each trajectory to an individual off-file. + */ + void exportTrajectories() { + Eigen::MatrixX3d mat; + for (int i = 0; i < p_cannonBallSim->getNumTrajectories(); i++) { + string filename = "trajectory" + to_string(i) + ".off"; + p_cannonBallSim->getTrajectories(i, mat); + if (mat.rows() <= 1) { + continue; + } + if (igl::writeOFF(filename, mat, Eigen::MatrixXi())) { + cout << "Wrote trajectory to " << filename << endl; + } else { + cout << "Failed to write trajectory to " << filename << endl; + } + } + } + + virtual void drawSimulationParameterMenu() override { + if (ImGui::Button("Export Trajectories", ImVec2(-1, 0))) { + exportTrajectories(); + } + ImGui::SliderAngle("Angle", &m_angle, -180.0f, 180.0f); + ImGui::InputFloat("Force", &m_force, 0, 0); + ImGui::InputFloat("Mass", &m_mass, 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 cannonball simulation + new CannonBallGui(); + + return 0; +} \ No newline at end of file diff --git a/1-2_spring/CMakeLists.txt b/1-2_spring/CMakeLists.txt new file mode 100644 index 0000000..6050d05 --- /dev/null +++ b/1-2_spring/CMakeLists.txt @@ -0,0 +1,43 @@ + +cmake_minimum_required(VERSION 3.1) +project(1-2_spring) + +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/1-2_spring/SpringSim.cpp b/1-2_spring/SpringSim.cpp new file mode 100644 index 0000000..9219f8b --- /dev/null +++ b/1-2_spring/SpringSim.cpp @@ -0,0 +1,50 @@ +#include "SpringSim.h" + +bool SpringSim::advance() { + // perform time integration with different integrators + + // use p_cube, m_spring, m_dt, m_gravity + + Eigen::Vector3d v = p_cube->getLinearVelocity(); + Eigen::Vector3d p = p_cube->getPosition(); + + // TODO + + // note that it is required to update both m_sptring.end and p_cube's position + switch (m_method) { + case 0: // analytical solution + { + break; + } + case 1: // explicit euler + + break; + + case 2: // symplectic euler + + break; + + case 3: // midpoint + + break; + + default: + std::cerr << m_method << " is not a valid integrator method." + << std::endl; + } + + // update spring end position + m_spring.end = p_cube->getPosition(); + + + // advance m_time + m_time += m_dt; + m_step++; + + // log + if ((m_step % m_log_frequency) == 0) { + m_trajectories.back().push_back(p_cube->getPosition()); + } + + return false; +} \ No newline at end of file diff --git a/1-2_spring/SpringSim.h b/1-2_spring/SpringSim.h new file mode 100644 index 0000000..b487980 --- /dev/null +++ b/1-2_spring/SpringSim.h @@ -0,0 +1,154 @@ +#include "Simulation.h" + +using namespace std; + +struct Spring { + public: + float stiffness; + float length; + float damping; + Eigen::Vector3d start; + Eigen::Vector3d end; +}; + +/* + * Simulation of a string with an attached mass. + */ +class SpringSim : public Simulation { +public: + SpringSim() : Simulation() { + init(); + m_trajectories.clear(); + m_trajectoryColors.clear(); + } + + virtual void init() override { + std::string path = "cube.off"; + m_objects.clear(); + m_objects.push_back(RigidObject(path)); + p_cube = &m_objects.back(); + + m_dt = 1e-2; + m_mass = 1.0; + m_log_frequency = 30; + m_method = 0; + m_gravity << 0, -9.81, 0; + + reset(); + } + + virtual void resetMembers() override { + p_cube->reset(); + m_spring.end = m_spring.start - m_spring.length * Eigen::Vector3d(0, 1, 0); + p_cube->setPosition(m_spring.end); + p_cube->setMass(m_mass); + + if (m_trajectories.size() == 0 || m_trajectories.back().size() > 1) { + m_trajectories.push_back(vector<Eigen::Vector3d>()); + m_trajectoryColors.push_back(m_color); + } else { + m_trajectoryColors.back() = m_color; + } + + setMethod(m_method); + } + + virtual void updateRenderGeometry() override { + p_cube->getMesh(m_renderV, m_renderF); + } + + virtual bool advance() override; + + virtual void renderRenderGeometry( + igl::opengl::glfw::Viewer &viewer) override { + viewer.data().set_mesh(m_renderV, m_renderF); + + for (size_t trajectory = 0; trajectory < m_trajectories.size(); + trajectory++) { + for (size_t point = 0; point < m_trajectories[trajectory].size(); + point++) { + viewer.data().add_points( + m_trajectories[trajectory][point].transpose(), + m_trajectoryColors[trajectory]); + } + } + + // draw the spring + for (int i = 0; i < 20; ++i) { + double w = i / 20.0; + Eigen::RowVector3d p = m_spring.start * w + m_spring.end * (1 - w); + viewer.data().add_points(p, Eigen::RowVector3d(0, 0, 0)); + } + } + + void clearTrajectories() { + m_trajectories.clear(); + m_trajectories.push_back(vector<Eigen::Vector3d>()); + m_trajectoryColors.clear(); + m_trajectoryColors.push_back(m_color); + } + +#pragma region SettersAndGetters + void setMass(double m) { m_mass = m; } + + void setSpring(const Spring &s) { + m_spring = s; + m_spring.end = + m_spring.start - m_spring.length * Eigen::Vector3d(0, 1, 0); + p_cube->setPosition(m_spring.end); + } + + void setMethod(int m) { + m_method = m; + switch (m_method) { + case 0: + m_color = Eigen::RowVector3d(1.0, 0.0, 0.0); + break; + case 1: + m_color = Eigen::RowVector3d(0.0, 1.0, 0.0); + break; + case 2: + m_color = Eigen::RowVector3d(0.0, 0.0, 1.0); + break; + case 3: + m_color = Eigen::RowVector3d(1.0, 1.0, 0.0); + break; + default: + std::cerr << m_method << " is not a valid integrator method." + << std::endl; + } + if (m_step == 0) { + m_trajectoryColors.back() = m_color; + } + } + + void setLogFrequency(int f) { m_log_frequency = f; } + + void getTrajectories(int index, Eigen::MatrixX3d &mat) const { + int num_points = m_trajectories[index].size(); + mat.resize(num_points, 3); + for (int i = 0; i < num_points; i++) { + mat.row(i) = m_trajectories[index][i]; + } + } + + int getNumTrajectories() const { return m_trajectories.size(); } +#pragma endregion SettersAndGetters + +private: + int m_method; // id of integrator to be used (0: analytical, 1: explicit euler, 2: semi-implicit euler, 3: midpoint) + double m_mass; + + Eigen::Vector3d m_gravity; + + Spring m_spring; + RigidObject *p_cube; + + Eigen::MatrixXd m_renderV; // vertex positions for rendering + Eigen::MatrixXi m_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; + vector<Eigen::RowVector3d> m_trajectoryColors; +}; \ No newline at end of file diff --git a/1-2_spring/main.cpp b/1-2_spring/main.cpp new file mode 100644 index 0000000..10699bc --- /dev/null +++ b/1-2_spring/main.cpp @@ -0,0 +1,123 @@ +#include <igl/writeOFF.h> +#include <thread> +#include "Gui.h" +#include "Simulator.h" +#include "SpringSim.h" + +/* + * GUI for the spring simulation. This time we need additional paramters, + * e.g. which integrator to use for the simulation and the force applied to the + * cube, and we also add some more visualizations (trajectories). + */ +class SpringGui : public Gui { + public: + // simulation parameters + float m_dt = 1e-2; + float m_mass = 1.0; + int m_log_frequency = 30; + + Spring m_spring; // stores properties of a spring + + SpringSim *p_springSim = NULL; + + const vector<char const *> m_integrators = {"Analytic", "Explicit Euler", "Symplectic Euler", "Midpoint"}; + int m_selected_integrator = 0; + + SpringGui() { + // initialize the spring to be used + m_spring.length = 5.0; + m_spring.stiffness = 5.0; + m_spring.damping = 0.1; + m_spring.start = m_spring.end = Eigen::Vector3d::Zero(); + + p_springSim = new SpringSim(); + p_springSim->setSpring(m_spring); + setSimulation(p_springSim); + + // show vertex velocity instead of normal + callback_clicked_vertex = + [&](int clickedVertexIndex, int clickedObjectIndex, + Eigen::Vector3d &pos, Eigen::Vector3d &dir) { + RigidObject &o = p_springSim->getObjects()[clickedObjectIndex]; + pos = o.getVertexPosition(clickedVertexIndex); + dir = o.getVelocity(pos); + }; + start(); + } + + virtual void updateSimulationParameters() override { + // change all parameters of the simulation to the values that are set in + // the GUI + p_springSim->setTimestep(m_dt); + p_springSim->setMethod(m_selected_integrator); + p_springSim->setLogFrequency(m_log_frequency); + p_springSim->setMass(m_mass); + p_springSim->setSpring(m_spring); + } + + virtual void clearSimulation() override { + p_springSim->clearTrajectories(); + } + + /* + * Writes each trajectory to an individual off-file. + */ + void exportTrajectories() { + Eigen::MatrixX3d mat; + for (int i = 0; i < p_springSim->getNumTrajectories(); i++) { + string filename = "trajectory" + to_string(i) + ".off"; + p_springSim->getTrajectories(i, mat); + if (mat.rows() <= 1) { + continue; + } + if (igl::writeOFF(filename, mat, Eigen::MatrixXi())) { + cout << "Wrote trajectory to " << filename << endl; + } else { + cout << "Failed to write trajectory to " << filename << endl; + } + } + } + + virtual bool childKeyCallback(igl::opengl::glfw::Viewer &viewer, + unsigned int key, int modifiers) override { + switch (key) { + case 'e': + case 'E': + exportTrajectories(); + return true; + // cicle through different integrators + case '>': + m_selected_integrator++; + m_selected_integrator %= m_integrators.size(); + return true; + case '<': + m_selected_integrator--; + m_selected_integrator = + (m_integrators.size() + m_selected_integrator) % + m_integrators.size(); + return true; + } + return false; + } + + virtual void drawSimulationParameterMenu() override { + if (ImGui::Button("Export Trajectories", ImVec2(-1, 0))) { + exportTrajectories(); + } + ImGui::InputFloat("Spring Stiffness", &m_spring.stiffness, 0, 0); + ImGui::InputFloat("Spring Length", &m_spring.length, 0, 0); + ImGui::InputFloat("Mass", &m_mass, 0, 0); + ImGui::InputFloat("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 spring simulation + new SpringGui(); + + return 0; +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 84afcec..54f8925 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,4 +27,7 @@ if (NOT LIBIGL_FOUND) find_package(LIBIGL REQUIRED QUIET) endif() -add_subdirectory(0_dummy) \ No newline at end of file +# add_subdirectory(0_dummy) +add_subdirectory(1-0_earth) +add_subdirectory(1-1_cannonball) +add_subdirectory(1-2_spring) \ No newline at end of file diff --git a/include/Simulation.h b/include/Simulation.h index 04e80ac..bb839c3 100644 --- a/include/Simulation.h +++ b/include/Simulation.h @@ -43,6 +43,7 @@ public: * touch any rendering data structures at all). You have to update the time * variables at the end of each step if they are necessary for your * simulation. + * Return true means the simulation is finished. */ virtual bool advance() = 0; -- GitLab