Assignment 9¶
本次作业实现的内容是粒子系统。通过简单的GUI,你可以与粒子系统互动,比如使用鼠标旋转或缩放场景。按下'p'键可以暂停或重新启动模拟,当模拟暂停时,按下's'键可以执行单步积分操作。'r'键允许你重启模拟,这会删除所有现有的粒子,并重置系统内部状态,包括随机数生成器的状态,确保每次模拟开始时条件一致。此外,提供了命令行选项来控制渲染方式,例如根据积分方案给粒子着色、绘制速度和加速度向量以及实现运动模糊效果,这些都有助于调试你的力场和积分方案。
Tasks¶
(1)完成基类 ForceField
然后分别实现四个基类 GravityForceField
ConstantForceField
RadialForceField
VerticalField
然后在这些类中实现函数
输入物体的位置,质量以及时间(上面的四类并未用到)
(2)完成基类 Integator
用于更新粒子的下一个状态,由于解的是常微分方程,被称为积分器。根据方法的不同,写出派生类 EulerIntegrator
MidpointIntegrator
以及 RungeKuttaIntegrator
,并完成函数
同时为了方便比较不同 Integrator
的求解效果,我们对不同的积分器设置不同的颜色,用 Vec3f getColor()
来返回
(3)最后一件事就是生成这些粒子,完成基类 Generator
,需要的函数有
// initialization
void Generator::SetColors(Vec3f color, Vec3f dead_color, float color_randomness);
void Generator::SetLifespan(float lifespan, float lifespan_randomness, int desired_num_particles);
void Generator::SetMass(float mass, float mass_randomness);
// on each timestep, create some particles
//新的粒子的数量
int Generator::numNewParticles(float current_time, float dt) const;
Particle* Generator::Generate(float current_time, int i);
// for the gui
//在fire example 中需要画一个大长方形
void Generator::Paint() const;
void Generator::Restart();
基类中包含的成员变量
Vec3f color, deadColor;
float colorRandomness;
float lifespan, lifespanRandomness;
int desiredNumParticles;
float mass, massRandomness;
Note
我感觉题目在这一块成员变量方面说的含糊不清,尤其注意一下 colorRandomness
的意思(后面的randomness类似)这个指的是生成的粒子的变化范围,
完成派生类 HoseGenerator
与 RingGenerator
相应的构造函数如下
HoseGenerator(Vec3f position, float position_randomness, Vec3f velocity, float velocity_randomness);
RingGenerator(float position_randomness, Vec3f velocity, float velocity_randomness);
Implementation¶
forcefield
¶
这里就展示一下 RadialForceField
相应的函数,这个描述了一个指向某个点的恒力场
class RadialForceField : public ForceField {
public:
RadialForceField(float magnitude) {
this->magnitude = magnitude;
}
Vec3f getAcceleration(const Vec3f &position, float mass, float t) const override {
auto dir = position;
dir.Normalize();
dir.Scale(-magnitude / mass, -magnitude / mass, -magnitude / mass);
return dir ;
}
private:
float magnitude;
};
其余场的实现方式均类似,注意除以质量
Integrator
¶
这个部分的内容主要是完成粒子更新的部分,根据相应的公式完成即可
void EulerIntegrator::Update(Particle *p, ForceField *forcefield, float t, float dt)
{
Vec3f pn = p->getPosition();
Vec3f vn = p->getVelocity();
Vec3f acceleration = forcefield->getAcceleration(pn, p->getMass(), t);
Vec3f new_velocity = vn + acceleration * dt;
Vec3f new_position = pn + vn * dt;
p->setVelocity(new_velocity);
p->setPosition(new_position);
p->increaseAge(dt);
}
void MidpointIntegrator::Update(Particle *p, ForceField *forcefield, float t, float dt)
{
Vec3f acceleration = forcefield->getAcceleration(p->getPosition(), p->getMass(), t);
Vec3f m_velocity = p->getVelocity() + acceleration * dt * 0.5;
Vec3f m_position = p->getPosition() + p->getVelocity() * dt * 0.5;
acceleration = forcefield->getAcceleration(m_position, p->getMass(), t + dt * 0.5);
Vec3f new_velocity = p->getVelocity() + acceleration * dt;
Vec3f new_position = p->getPosition() + m_velocity * dt;
p->setVelocity(new_velocity);
p->setPosition(new_position);
p->increaseAge(dt);
}
void RungeKuttaIntegrator::Update(Particle *p, ForceField *forcefield, float t, float dt)
{
Vec3f pos = p->getPosition();
Vec3f vel = p->getVelocity();
float mass = p->getMass();
// 计算加速度
auto getAcceleration = [&](const Vec3f& position, const Vec3f& velocity, float time) {
return forcefield->getAcceleration(position, mass, time);
};
// 计算 k1
Vec3f k1_v = getAcceleration(pos, vel, t) * dt;
Vec3f k1_x = vel * dt;
// 计算 k2
Vec3f k2_v = getAcceleration(pos + k1_x * 0.5f, vel + k1_v * 0.5f, t + dt * 0.5f) * dt;
Vec3f k2_x = (vel + k1_v * 0.5f) * dt;
// 计算 k3
Vec3f k3_v = getAcceleration(pos + k2_x * 0.5f, vel + k2_v * 0.5f, t + dt * 0.5f) * dt;
Vec3f k3_x = (vel + k2_v * 0.5f) * dt;
// 计算 k4
Vec3f k4_v = getAcceleration(pos + k3_x, vel + k3_v, t + dt) * dt;
Vec3f k4_x = (vel + k3_v) * dt;
// 更新位置和速度
Vec3f new_position = pos + (k1_x + 2.0f * k2_x + 2.0f * k3_x + k4_x) *(1 / 6.0f);
Vec3f new_velocity = vel + (k1_v + 2.0f * k2_v + 2.0f * k3_v + k4_v) *(1 / 6.0f);
p->setPosition(new_position);
p->setVelocity(new_velocity);
p->increaseAge(dt);
}
Generator
¶
两种产生方式的不同点,一是产生的粒子数目不同,二是产生粒子的位置不同
int HoseGenerator::numNewParticles(float current_time, float dt) const {
return ceil(dt * desiredNumParticles / lifespan );
}
//乘上时间是为了保证粒子的密度保持不变
int RingGenerator::numNewParticles(float current_time, float dt) const override{
return ceil(dt * desiredNumParticles / lifespan * current_time);
}
Particle* HoseGenerator::Generate(float current_time, float dt, int i){
Random rand(int(std::chrono::high_resolution_clock::now().time_since_epoch().count()));
Vec3f pos = position + rand.randomVector() * position_randomness;
Vec3f vel = velocity + rand.randomVector() * velocity_randomness;
Vec3f col = color + rand.randomVector() * colorRandomness;
col.Clamp();
Vec3f dead_col = deadColor + rand.randomVector() * colorRandomness;
dead_col.Clamp();
float m = mass + rand.next() * massRandomness;
float lifespan = this->lifespan + rand.next() * lifespanRandomness;
return new Particle(pos, vel, col, dead_col, m, lifespan);
}
Particle* RingGenerator::Generate(float current_time, float dt, int i){
Random rand(std::chrono::high_resolution_clock::now().time_since_epoch().count());
Vec3f col = color + rand.randomVector() * colorRandomness;
col.Clamp();
Vec3f dead_col = deadColor + rand.randomVector() * colorRandomness;
dead_col.Clamp();
float m = mass + rand.next() * massRandomness;
float lifespan = this->lifespan + rand.next() * lifespanRandomness;
float radius = current_time;
int numP = numNewParticles(current_time, dt);
float theta = float (i) * 2 * M_PI / numP;
//围绕一个圆生成
Vec3f pos = Vec3f(radius * cos(theta), 0, radius * sin(theta));
pos += pos + rand.randomVector() * position_randomness;
Vec3f vel = velocity + rand.randomVector() * velocity_randomness;
return new Particle(pos, vel, col, dead_col, m, lifespan);
}
Summary¶
这次的作业与之前不同,实现的是动画,包括了粒子的生成,更新与擦除。同时以一种接近物理的方式完成粒子的动画。完成的内容难度适中,遇到的bug也不太多,主要遇到的问题就是取随机数时按照时间取速度太慢,后面采取的是高分辨率的时间值。
终于肝了一个寒假之后,在开学后第三天完成所有作业文档,完美收官!