And now for something completely different: physics simulation for particles.
A particle system in Scotty3D is simply a collection of non-interacting, physics-simulated, spherical particles that interact with the rest of the scene. Take a look at the [user guide](../guide/simulate.md) for an overview of how to create and manage them.
## Ray traced physics
In this task, you will implement ``bool Particle::update(const PT::BVH<PT::Object>& scene, float dt, float radius)`` found in `student/particles.cpp`.
## Kinematics
Each particle is described by a position and a velocity. If we want to know where the particle will be at some future time, we simply have to move it along its velocity vector. If the particle is travelling at constant velocity, this is a very easy expression to compute.
However, if the particle can have arbitrary forces applied to it, we no longer know exactly how it will move. Since we're interested in physical simulation, we can apply the basic equation governing motion: F = ma. Since acceleration is the time derivative of velocity, we now have a differential equation governing how our particle moves. Unfortunately, it's no longer feasible (in general) to compute this motion analytically, so instead we will use numerical integration to step our particles forward in time. To get realistic behavior from approximate but easy to compute steps, we can simply take many small time steps until we reach the desired destination.
<center><imgsrc="task4_media/fma.png"></center>
There are many different techniques for integrating our equations of motion, including forward, backward, and symplectic Euler, Verlet, Runge-Kutta, and Leapfrog. Each strategy comes with a slightly different way of computing how much to update our velocity and position across a single time-step. In this task, we will use the simplest - forward Euler - as we aren't too concerned with stability or energy conservation for our particle system. Forward Euler simply steps our position forward by our velocity, and then velocity by our acceleration:
<center><imgsrc="task4_media/euler.png"></center>
In `Particle::update`, use this integrator to step the current particle forward for `dt` seconds. Note that the only force we will apply to our particles is gravity, so `acceleration` (a static member of `Particle`) is a constant `-9.8` meters per second squared pointing downward (feel free to play around with this!). Once you've added the update rule, you should already see some interesting behavior - create a particle system and see how the particles travel along nice parabolic trajectories.
## Collisions
The more substantial part of this task is colliding each particle with the rest of our scene geometry. Thankfully, you've already done most of the work required here during A3: we can use Scotty3D's ray-tracing capabilities to find collisions along our particles' paths.
During each timestep, we know that in the absence of a collision, our particle will travel in the direction of velocity for distance `||velocity|| * dt`. We can create a ray representing this position and velocity and use its distance bound to restrict how far we travel this time-step. If the ray intersects with our scene, we know if and when the particle experiences a collision. Note that since we are representing particles as small spheres, you must take `radius` into account when looking for collisions. If the center of our particle can see collisions up to distance `||velocity|| * dt`, what distance can the closest point on the sphere collide up to?
If our ray hit the scene, we can use its hit distance to figure out at what time our particle hit the surface. Again, be careful about the radius here. (Where was the center of the particle when the closest point started intersecting?) We could just place the particle at the collision point and be done, but we don't want our particles to simply stick the surface! Instead, we will assume all particles collide elastically (and massless-ly) - that is, the magnitude of their velocity should be the same before and after the collision, and its direction should be reflected about the normal of the collision surface.
Finally, once we have a reflected velocity, we can compute how much of the time step remains after the collision, and step the particle forward that amount. However, what if the particle collided with the scene _again_ before the end of the time-step? If we are using very small time-steps, it might be OK to ignore this possibility, but we want to be able to resolve all collisions. So, we can repeat the ray-casting procedure in a loop until we have used up the entire time-step (up to some epsilon). Remember to only use the remaining portion of the time-step each iteration, and to step forward both the velocity and position at each sub-step.
Once you have got collisions working, you should be able to open `particles.dae` and see a randomized collision-fueled waterfall. Try rendering the scene!
## Lifetime
Finally, note that `Particle::update` is supposed to return a boolean representing whether or not the particle should be removed from the simulation. Each particle has an `age` member that represents the remaining time it has to live. Each time-step, you should subtract `dt` from `age` and return whether `age` is still greater than zero.
@@ -18,7 +18,7 @@ To add an emitter, open the dropdown menu, adjust desired parameters, and press
- Scale: the scale factor to apply to the particle mesh when rendering particles.
- Lifetime: how long (in seconds) each particle should live before it is deleted.
- Particles/Sec: how many particles should be generated per second. The total amount of live particles is hence `lifetime * particles_per_second`.
- Particle: choose the shape of each particle. If mesh objects are present in the scene, they will also show up here, allowing the creation of particles with custom shapes.
- Particle: choose the shape of each particle. If mesh objects are present in the scene, they will also show up here, allowing the creation of particles with custom shapes!
- Enabled: whether to immediately enable the emitter
Once an enabled emitter is added to the scene (and animation task 4: particle simulation is implemented), particles will start generating and following trajectories based on the emitter parameters. Particles should collide with scene objects. When moving existing objects that particles interact with, the simulation will not be updated until the movement is completed.
@@ -18,7 +18,7 @@ Now that you have implemented the ability to sample more complex light paths, it
**Note:** In the BSDF diagrams and documentation below, both the `out_dir` and the returned in-direction are pointing away from the intersection point of the ray and the surface, as illustrated in this picture below. This is so that it is easy to define the angles with respect to the surface normal.
Also, remember that in pathtracing, we are tracing _backwards_, from the scene to the camera, which is why the output of these BSDF diagrams (and hence your BSDF functions) correspond with the input rays of the pathtracing procedure.
Also, remember that in pathtracing, we are tracing _backwards_, from the camera to the scene, which is why the output of these BSDF diagrams (and hence your BSDF functions) correspond with the input rays of the pathtracing procedure.