跳转至

Assignment 9

文本统计:约 697 个字 • 140 行代码

本次作业实现的内容是粒子系统。通过简单的GUI,你可以与粒子系统互动,比如使用鼠标旋转或缩放场景。按下'p'键可以暂停或重新启动模拟,当模拟暂停时,按下's'键可以执行单步积分操作。'r'键允许你重启模拟,这会删除所有现有的粒子,并重置系统内部状态,包括随机数生成器的状态,确保每次模拟开始时条件一致。此外,提供了命令行选项来控制渲染方式,例如根据积分方案给粒子着色、绘制速度和加速度向量以及实现运动模糊效果,这些都有助于调试你的力场和积分方案。

Tasks

(1)完成基类 ForceField 然后分别实现四个基类 GravityForceField ConstantForceField RadialForceField VerticalField 然后在这些类中实现函数

virtual Vec3f ForceField::getAcceleration(const Vec3f &position, float mass, float t) const = 0;

输入物体的位置,质量以及时间(上面的四类并未用到)

(2)完成基类 Integator 用于更新粒子的下一个状态,由于解的是常微分方程,被称为积分器。根据方法的不同,写出派生类 EulerIntegrator MidpointIntegrator 以及 RungeKuttaIntegrator ,并完成函数

virtual void Update(Particle *particle, ForceField *forcefield, float t, float dt) = 0;

同时为了方便比较不同 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类似)这个指的是生成的粒子的变化范围,

完成派生类 HoseGeneratorRingGenerator 相应的构造函数如下

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也不太多,主要遇到的问题就是取随机数时按照时间取速度太慢,后面采取的是高分辨率的时间值。

终于肝了一个寒假之后,在开学后第三天完成所有作业文档,完美收官!

评论区

对你有帮助的话请给我个赞和 star => GitHub stars
欢迎跟我探讨!!!