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