CS 467 - Random Walk

Due: 2020-03-05 5:00p

Goals

  • Learn how to do a random walk
  • Learn about p5.Vector and associated operations
  • Learn how to use blur to make things glow

Prerequisites

  1. Accept the assignment on our Github Classroom.
  2. Download the git repository to your local computer.

Assignment

In this practical, you are going to write a basic random walk, and then modify it to achieve something like this (it is dynamic, so a still image doesn't quite capture it):

Practical 3 Goal

Links to the references for the included functions can be found at the bottom.

Part 1: Write a basic random walk

The draw() function for our basic random walk has only a few basic duties.

  • generate a new random offset (which should be fairly small)
  • create a new point by adding the offset to the previously saved position
  • draw a line from the previous point to the new point
  • save the new point for next time

To make our lives easier, we are going to make use of p5.js' Vector tools.

Create a new global variable called position.

In setup(), initialize the position to the center of the canvas using createVector().

To generate the random offset, you can use p5.Vector.random2D(), which will generate a random 2D vector in which the x and y are both set to a random value in the range [0,1). To adjust the magnitude of the vector, use the vector's mult() function, which will allow you to scale the vector by a scalar value (i.e., multiply both components by the input value).

Create a new point by adding the offset vector to position.

Important note: There are two ways to do math with p5.js' vectors. Given two vectors v1 and v2, we can add them together with the object method v1.add(v2). This will do component-wise addition, and update v1 with the result. There is also a static version of the functions available on p5.Vector. These return a new vector and leave the originals alone. So, you would write const v3 = p5.Vector.add(v1, v2). In the above, you can do the object method for the mult(), but you should use the static version for the addition since you still need the original point to draw the line.

Draw the line between the two points (individual components of a vector v are available as v.x and v.y).

Save the new point as position.

So we can see the movement, make sure to call background in the setup() function instead of the draw() function.

You should now have a random walk drawing.

Part 2: Make it shine

The random walk is actually interesting in and of its own right, but we are going to spice it up and learn some new tricks along the way.

In computer graphics, when we draw things that are supposed to be light sources, they can look dull and flat, even if they are pure (255,255,255). This is because we are used to it being hard to look at bright things, and they make their surroundings glow. For example, if you look at a light bulb that is switched on, it is difficult to see its edges clearly. If we draw that with crisp lines, it just looks like white paint, not like an emissive surface.

An old technique in graphics is to add some blur around things that should glow. This is usually done as a three step process. First, draw the light source. Then add blur to fuzz it up. Finally, draw the light source again on top. This gives it the appearance of having crisp edges, while still spilling out over its surroundings (all without having to do costly lighting calculations).

We are going to do a variant of this to make our random walker into a little comet or spark.

First, switch the background to black and the stroke to some bright color like yellow or orange.

Next set the stroke weight to something thick (try 5). This will give us a good base to blur. Draw the line.

Now, blur the canvas. We can do this with the filter method. The filter() method performs pixel level operations on the entire canvas. In this case, call filter(BLUR, 2). This says to apply the blurring operation to the entire canvas with a blur radius of 2.

The next step is to set the stroke weight back to 1 and draw the same line again.

It is important to get this order right: draw fat line, blur, draw skinny line. This gives us a nice crisp line with a halo around it. If the halo doesn't fade you probably put the blur first. If you don't see a central bright core, then you put the blur after the second line.

An interesting facet of this is that because we are blurring the whole canvas, the old segments of the trail will slowly fade away over time as the blur diffuses them.

Test this out and tune the parameters (stroke weight, blur amount, etc) to your aesthetic taste.

I suggest committing your changes to git at this point.

Part 3: Give it some friends

To get the effect I showed above, you need to add more walkers. You have done all of the hard work, this is just about repetition.

Replace the position variable with an array called creatures, and give yourself a constant NUM_CREATURES that controls the number of walkers (there are 100 in the image above).

In setup(), load up the array with the appropriate number of creatures. You can represent each create as an object with a color, a position, and a next (this will allow us to color the creatures individually). For those of you new to JavaScript, we can make literal objects that look like dictionaries in Python (e.g., {color:'yellow', position: createVector(width/2, height/2), next:null}). Syntax-wise, the only real difference is that the keys aren't strings.

In the example object, I set the color to 'yellow', but I recommend randomizing it. To get the look in the picture, I set the color mode to HSB and limited the hue to a band where the yellow and oranges are (roughly 20-60 degrees).

The tricky part is updating the draw function. You need at least two different loops. The first loop should visit each creature, generate the next position, and draw the fat line. Then you blur the canvas. Finally you do a second loop to draw the second line and set the next position. We need that next property on the objects so we can save all of those secondary points across the two loops. If we tried to do this with one loop, the first creature drawn would be completely blurred out by the time we drew the last one.

Try it out. Adjust the color and number of creatures to your liking.

Part 4: Wrapping up

This is not a required part of the practical.

In this form, it looks pretty cool, and it will be a dynamic form for some time. Eventually, however, the walkers will walk offscreen. Sometimes they will come back. If you wait long enough, they will eventually all disappear. This is a phenomenon called the "gambler's ruin" (A gambler with a finite amount of money will eventually lose everything when playing a fair game against a house with infinite funds -- gambling is a different kind of random walk).

To address this, there are a variety of things you could do. You could wrap the canvas, so walking off one side causes it to appear on the other. Or you could detect when they have crossed the boundary and reset them to the middle. Or you could simply forbid them to leave. Any of these approaches would keep all of the little wriggling things on the screen for the duration of the run.

Finishing up

Commit your changes to git and push them back up to GitHub. I will find them there.

References

Links to the reference pages for the functions you will be using:

background
colorMode
createCanvas
createVector
filter
line
random
stroke
strokeWeight
p5.Vector