CPSC 120 | Principles of Computer Science | Fall 2024 |
Labs are due at the start of class. It is OK if you show up and copy your files to the handin directory at the very beginning of class, but this should take at most a couple of minutes and you should not spend the next lab period finishing up the previous week's lab.
Loops expand our ability to easily draw lots of similar things when there is some kind of pattern in the positioning, color, size, and orientation of those things — stripes on a road, a flock of birds, a patch of wildflowers, shapes in a quilt block, etc.
Arrays expand our ability to easily animate lots of similar things in similar ways by making it possible to write loops where what is changing from one repetition to the next is (or includes) the animation variables themselves — arrays are used when each thing would need its own set of animation variables because the values may be different for different things (without a pattern).
This week's lab focuses on working with arrays using a development strategy intended to simplify the task of animating lots of things: first figure out the drawing and animating for a single thing, then transform the single-thing solution to a many-thing solution by adding arrays.
Animating lots of things has many applications in computer graphics. One such example is particle systems, a technique used to model complex natural phenomena like fire, smoke, water, falling leaves, clouds, snow, dust, hair, and fur using huge numbers of very small particles. The fireworks in the demo below and in exercise 3 are inspired by the idea of particle systems and exercise 2 uses a basic particle system to create the spark plume. Next week's lab will explore behavioral animation, a technique where simple rules applied to individuals can give rise to complex group behaviors often observed in the natural world.
Successfully completing this lab means that you are able to:
The short version:
Help with learning the process of constructing programs is fine; shortcutting the process and arriving at a result that you didn't produce yourself or don't fully understand how to produce is not.
Always attempt the problem yourself first, using this lab handout, the materials from class posted on the schedule page, and the assigned reading in the textbook.
Your primary resources for help should be office hours and the Teaching Fellows.
You may not work with other students to write code together.
You may not shortcut to a solution by copying code (except as specifically authorized in instructions) or using someone else's program as a guide or to understand what yours should be like even if you don't directly copy anything, You may not be in possession of someone else's program or solution before you have handed in your own.
You must document any help received (including from TFs) and any resources used other than the textbook and posted course materials. Put comments in your sketch indicating who helped (or the source used) and how / with what.
Make sure that you understand not only the result achieved, but also how one knows what to do to achieve that result. This gets at the process of writing a sketch — identifying what code elements are needed, filling in the details for a particular task, and pulling it all together.
Review the discussion in previous lab handouts and the full collaboration policy for more details.
To hand in your work:
Make sure that your name and a short description of the sketch are included in a comment at the beginning of each sketch.
Make sure that you've auto-formatted each sketch.
Copy the entire lab10a, lab10b, lab10c directories from your sketchbook (~/cs120/sketchbook) to your handin directory (found inside /classes/cs120/handin).
If you did any of the extra credit, also copy the entire lab10d, lab10e, and/or lab10f directories from your sketchbook to your handin directory.
As always, refer to the class materials (slides, in-class exercises handouts, and examples) posted on the schedule page. In particular, see this week's slides on array-ification, and compare the before-and-after versions of the examples in the slides and the posted solutions for the in-class exercises.
Put your name and a description of the sketch in comments at the beginning of each sketch. Also don't forget to Auto Format your code before handing it in. (Refer back to lab 2 for more on Auto Format if needed.)
This lab is about creating sketches with many animated things using the array-ify process discussed in class. Be sure to follow the "to create this sketch" steps in each exercise! In all three exercises, you have been provided with a complete sketch that animates one item — array-ify it and make other modifications as directed in each exercise rather than writing a new sketch from scratch. Little credit will be given if this process is not followed.
Create a sketch called lab10a which produces an animation similar to the one shown — there should be 50 circles, each of which starts in a random position and then, one by one, starts to move after a delay. Stagger the delays so that the first circle starts to move after 40 frames, the next after 80 frames, the next after 120 frames, etc. (Click to reset the demo; you do not need to be able to click to reset in your sketch.)
To create this sketch:
Create a new sketch, paste in this code (where a circle starts to move after a delay), add your name and a description of the sketch in comments at the beginning, and save your sketch as lab10a.
Run the sketch to see what the provided code does, and see the About Countdown Timers section below for more information about what is going on in this code (in particular, what the timer variable is for).
Modify the sketch to have 50 circles, each of which, like the one in the provided code, starts in a random position and then starts to move after a delay. (Use the array-ify process!) As you decide on which animation variables will become array variables, plan for each circle to have its own delay (to achieve the staggered starts) but for now still initialize each of those delays to 200 (as in the provided code).
Change the initialization of the delays so that the first circle starts to move after 40 frames, the next after 80 frames, the next after 120 frames, etc.
In this sketch, the animation starts after a delay instead of as soon as the sketch starts. This delayed start can be implemented using a variation of the state machine pattern where a countdown variable is used in place of the state variable. The countdown variable is an int which is initialized to some value greater than 0. Update the countdown by subtracting 1, and use it by doing the action when its value is less than or equal to 0.
In the provided code for this exercise, the countdown variable is called timer. Observe that it is initialized to 200 in setup(), so that the ellipse will start moving after 200 frames. Also observe the update and use steps in draw() — the value of timer is decreased by 1 (update) and the ellipse's x coordinate is updated when the countdown has reached 0 (use).
Create a sketch called lab10b which produces an animation of a plume of sparks. (The picture below is just a single snapshot; in the completed sketch, the plume will move to the right and gradually shift colors from red through the rainbow to purple.)
To create this sketch:
Create a new sketch, paste in this code (which animates a single spark in the plume), add your name and a description of the sketch in comments at the beginning, and save your sketch as lab10b.
You can run the sketch to see what the provided code does, though it won't be very interesting for just a single spark (and it may be hard to even see the one very small spark). See the About Particle Systems and About HSB Color sections below for more information about what is going on in this code.
Modify the sketch so that there are 30000 sparks, each of which is animated like the one in the provided code. (Depending on the speed of your computer and how much memory it has, you may need to decrease the 30000 value so that the sketch runs at a reasonable speed.)
Note: Only array-ify animation variables for spark properties! (Which variables are those?)
Particle systems have their origins in special effects for movies. They were pioneered in the early 1980s by William Reeves at Industrial Light and Magic and first introduced in 1982 in Star Trek II: The Wrath of Khan for the Genesis effect. (watch the clip from the movie)
In a nutshell, a particle system consists of a large number of individual particles which have animated properties such as position, speed, color, transparency, size, and lifespan and which are drawn as very small shapes (such as an ellipse or short time) plus a set of rules for how particles are animated. The lifespan defines how long a particle remains visible — at the end of its lifespan, the particle dies off and is replaced by a new particle. New particles are produced by an emitter which defines how the particle's properties are initialized. The emitter itself can be animated so that later particles are generated with different properties from earlier ones.
In this sketch, particles have position, speed, color, transparency, and a noise parameter t. Noise is used to provide a smooth random variation when each particle's x coordinate is updated so that they waft upwards in a more realistic-looking way rather than moving in a straight line. The emitter defines the x coordinate and color (hue) of the new particles. Both of these properties of the emitter are animated so the location of the plume and its color change over the course of the animation.
Particles die off when they become fully transparent and are thus no longer visible. The update part of draw is thus at its core a two-alternative on-the-spot decision — if the particle is still visible, update its properties (position, speed, transparency, etc), and if not, reset its properties to those for a newly-generated particle (effectively replacing the old particle with a new one). So that the particles aren't all replaced at the same time, dead particles are not immediately resurrected. This is handled by the
else if ( random(0,100) <= 1 )
part of the conditional in draw. random(0,100) generates a random number between 0 and 100; there's only a small chance that the number generated will be less than or equal to 1 so in each frame only a few of the dead particles are replaced. This makes for a much better-looking effect.
You might notice the statement colorMode(HSB) in setup() and the animation variables named hue, saturation, and brightness relating to color. RGB is just one way to describe colors, via the combination of red, green, and blue light. HSB (or HSV) is another color model which aligns more closely with how humans perceive and describe colors — hue captures the color itself (red, orange, yellow, etc), saturation captures the intensity of the color, and brightness (or value) captures how light or dark a color is. HSB is used instead of RGB for this sketch because it makes it easy to smoothly change the color (hue) without affecting how intense or bright the color is (saturation and brightness).
HSB/HSV color model | RGB color model |
images source |
Create a sketch called lab10c which produces an animation of a series of exploding fireworks like the demo at the beginning of this handout (but without the fading trails and moon and mountains scenery).
To create this sketch:
Create a new sketch, paste in this code (which animates a single spark in the fireworks display), add your name and a description of the sketch in comments at the beginning, and save your sketch as lab10c.
Run the sketch to see what the provided code does, and see the About Fireworks section below for more information about what is going on in this code.
Modify the sketch so that there are 1000 sparks, each of which is animated like the one in the provided code.
Note: Only array-ify animation variables for spark properties!
Note: What exactly goes inside the array loops is important — updating or setting the value of animation variables that are not arrays should not be inside an array loop.
Note: There are two local (non-array) variables — angle in draw() and launchx in reset(). When the firework explodes (the transition from the launch stage to the exploding state), every spark's speed should be updated with a different random value — so the declaration and initialization of angle should be included in the array loop that updates each spark's speed. When the firework is reset (the transition from exploding to launch), every spark should get the same x coordinate so that the whole firework looks like just a single point during the launch. This means that there should be a single value of launchx for all sparks — so the declaration and initialization of launchx should not be included in the array loop that (re)sets each spark's position.
The fireworks sketch is a two-stage animation — there's a launch stage where the spark(s) move upwards, and an explosion stage where the spark(s) fall back towards the ground. This is an example of a state machine with two states (launch and explode) and the boolean state variable exploding keeps track of the current stage. Look for the other elements of the state machine pattern in the provided code — actions that only take place in one of the stages and the transition from one state to another.
The transition between stages is based on the passage of time, so a countdown timer named timer is used to keep track of this. Since the same timer is used for both stages, switching between stages is based on both the timer reaching 0 and the current stage.
There's some initialization that needs to be done for each stage — transitioning from one state to another isn't just a matter of setting the state variable to a new value. Note those extra things going on in the "handle stage transitions" code.
You can earn extra credit by going substantially beyond the required elements. Some possibilities:
Create a sketch lab10d which is a copy of your sketch from #3 (the fireworks) and do one or more of the following to make it look more like the demo at the beginning of this lab:
Add scenery such as mountains, plants, or other elements. It is fine to reuse elements from your earlier labs (or create new things).
Add variety:
Choose a new random color for the firework each time it is launched. (Use the same color for all of the sparks, not a separate one for each.)
Vary the color of the sparks: each time the firework is launched, choose a new random color as the base color, then give each spark a random color near the base color. To do this, add a small random amount to each color component of the base color for each spark — include negative numbers in the range so the new color can be brighter or darker than the base color.
Add animated, repeated scenery elements which make appropriate use of arrays. (Maybe a busy highway with a bunch of cars?) "Appropriate use of arrays" means that you can't easily accomplish the animation with just one set of variables — each repeated thing needs its own.
Have multiple fireworks at once, though they can all launch and explode at the same time. Do not add separate arrays for position, speed, transparency, etc for each of the fireworks — just add more sparks to the existing arrays but initialize different chunks of the arrays differently..
Have multiple fireworks at once which launch and explode at different times. Do not add separate arrays for position, speed, transparency, etc for each of the fireworks — just add more sparks to the existing arrays and use clever initialization. You will need to array-ify the timer and exploding variables so each firework has its own copy.
Real fireworks leave a bit of a trail, like in the demo at the top of this page. You can get a full trail by only clearing the background at the beginning of the sketch instead of every time a frame is drawn; some clever use of partially-transparent rectangles can achieve the fading-trail effect. (You don't need extra arrays for this.)
Create a sketch lab10e which is a copy of your sketch from #2 (the plume) and add multiple emitters so there are several plumes at once. Array-ify the emitter variables but you can continue to use the same arrays for the sparks — when a spark is reset, randomly choose which emitter to use for the x coordinate and hue. This requires accessing a slot in an array outside of a go-through-every-slot loop.
Create a new sketch called lab10f where a trail of ellipses follows the mouse. The idea is that the last 50 mouse positions will be stored (x coordinates in one array, y coordinates in another) and this information will be used to draw the ellipses. Each time the mouse moves, the current position will be added and the oldest position forgotten. This is a different pattern for the update step than with the array-ify process — instead of updating each slot based on its old value, each slot's new value will be the old value from the slot to its right (effectively shifting the values to the left, as shown below) and the new position will be stored in the last slot. Don't use the clever partially-transparent arrays trick hinted at for the fireworks — you don't want a fading trail here.
More challenging elements will earn more points.