差不多過了四個月(一個學期)了,這個學期太忙了,之前一直沒有寫出作業8,再加上這個學期,太折磨了,幾乎就是在荒廢時間,暑假在忙保研的事情,其實還有一個營,不過差不多就要確定了吧,於是最終在今天寫完了作業8。
同樣的,我們來看作業要求:
在 rope.cpp 中, 實現 Rope 類的構造函數。這個構造函數應該可以創建一個
新的繩子 (Rope) 對象,該對象從 start 開始,end 結束,包含 num_nodes 個節點。
每個結點都有質量,稱為質點;質點之間的線段是一個彈簧。通過創建一系列的
質點和彈簧,你就可以創建一個像彈簧一樣運動的物體。
pinned_nodes 設置結點的索引。這些索引對應結點的固定屬性 (pinned at
tribute) 應該設置為真(他們是靜止的)。對於每一個結點,你應該構造一個 Mass
對象,並在 Mass 對象的構造函數里設置質量和固定屬性。(請仔細閱讀代碼,確
定傳遞給構造函數的參數)。你應該在連續的兩個結點之間創建一個彈簧,設置彈
簧兩端的結點索引和彈簧系數 k,請檢查構造函數的簽名以確定傳入的參數。
這個應該很簡單,我們要注意,彈簧的數量比節點的數量多一個,這樣就很簡單了,簡單向量計算即可
Rope::Rope(Vector2D start, Vector2D end, int num_nodes, float node_mass, float k, vector<int> pinned_nodes) { // TODO (Part 1): Create a rope starting at `start`, ending at `end`, and containing `num_nodes` nodes. for(int i=0;i<num_nodes;i++) { Vector2D current = start + i*(end-start)/(num_nodes-1); Mass* tmp = new Mass(current,node_mass,false); masses.push_back(tmp); } for(int i=0;i<num_nodes-1;i++) { Spring* tmp = new Spring(masses[i],masses[i+1],k); springs.push_back(tmp); } // Comment-in this part when you implement the constructor for (auto &i : pinned_nodes) { masses[i]->pinned = true; } }
接下來是實現胡克定律和隱式/顯式歐拉,這里有一個問題,就是我的顯式歐拉似乎一直不穩定,但是隱式歐拉是穩定的
基於物理,注意,走入CGL/include 文件夾,其實我們能看到作業框架引用的一系列函數庫,計算向量的模長可以用norm()函數,而計算向量的歸一化可以用unit函數,屬實節省了不少時間。
注意,顯式的歐拉要求我們按照原來的速度算位置,而隱式先讓我們算好后面的速度然后計算最終位置
這里最令人難以理解的就是“阻尼”的計算,按照閆神課上講的,我們應該在Springs循環算每一個阻尼,作業pdf這里沒講明白,其實在論壇上助教指出很簡單,就是做一個速度的衰減就行了,具體的討論可以看這兩個帖子
HW8的一些問題 – 計算機圖形學與混合現實研討會 (games-cn.org)
關於作業8的一些問題解答 – 計算機圖形學與混合現實研討會 (games-cn.org)
void Rope::simulateEuler(float delta_t, Vector2D gravity) { for (auto &s : springs) { // TODO (Part 2): Use Hooke's law to calculate the force on a node Vector2D ab = s->m2->position - s->m1->position; Vector2D f = s->k * (ab.unit()) * (ab.norm() - s->rest_length); s->m1->forces += f; s->m2->forces -= f; } for (auto &m : masses) { float k_d = 0.1; if (!m->pinned) { // TODO (Part 2): Add the force due to gravity, then compute the new velocity and position m->forces += gravity * m->mass; // TODO (Part 2): Add global damping m->forces += - k_d * m->velocity; Vector2D a = m->forces / m->mass; /* //explicit Euler m->position += m->velocity * delta_t; m->velocity += a * delta_t; */ //implicit Euler m->velocity += a * delta_t; m->position += m->velocity * delta_t; } // Reset all forces on each mass m->forces = Vector2D(0, 0); } }
然后是最后的顯示Verlet方法,注意我們這里會用到mass中的last_position參數,用於保存X(t-1),其實這里很簡單,就是把前面的一部分復制過來,然后按照作業要求寫出最關鍵的那一行帶阻尼的公式就行,注意用last_position進行記憶原先的值即可
void Rope::simulateVerlet(float delta_t, Vector2D gravity) { for (auto &s : springs) { // TODO (Part 3): Simulate one timestep of the rope using explicit Verlet (solving constraints) Vector2D ab = s->m2->position - s->m1->position; Vector2D f = s->k * (ab.unit()) * (ab.norm() - s->rest_length); s->m1->forces += f; s->m2->forces -= f; } for (auto &m : masses) { if (!m->pinned) { m->forces += gravity * m->mass; Vector2D a = m->forces / m->mass; // TODO (Part 3.1): Set the new position of the rope mass Vector2D temp = m->position; // TODO (Part 4): Add global Verlet damping double damping_factor = 0.00005;
// 核心公式,帶阻尼的計算 m->position = m->position + (1 - damping_factor) * (m->position - m->last_position) + a * delta_t *delta_t;
// 用last_position記憶原先的位置 m->last_position = temp; } m->forces = Vector2D(0,0); } } }
可以看一下最終的效果