games101 作业8
差不多过了四个月(一个学期)了,这个学期太忙了,之前一直没有写出作业8,再加上这个学期,太折磨了,几乎就是在荒废时间,暑假在忙保研的事情,其实还有一个营,不过差不多就要确定了吧,于是最终在今天写完了作业8。
同样的,我们来看作业要求:
在 rope.cpp 中, 实现 Rope 类的构造函数。这个构造函数应该可以创建一个新的绳子 (Rope) 对象,该对象从 start 开始,end 结束,包含 num_nodes 个节点。每个结点都有质量,称为质点;质点之间的线段是一个弹簧。通过创建一系列的质点和弹簧,你就可以创建一个像弹簧一样运动的物体。
pinned_nodes 设置结点的索引。这些索引对应结点的固定属性 (pinned attribute) 应该设置为真(他们是静止的)。对于每一个结点,你应该构造一个 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); } } }
可以看一下最终的效果