This week’s lab is a departure from the usual “printing text on a console” that I’m sure everyone just loves. Instead, you’ll be making an interactive graphical program. Yes, really! See that animation on the right? That’s what you’re making.

What is this?

A particle system is a way to simulate collections of small objects called “particles” which all behave the same way. Each particle on its own is not too interesting, but put enough of them together, and you can get something really beautiful. In the animation to the right, it looks like we’re spraying droplets of water or something. Each droplet is a particle.

Particle systems are ubiquitous in computer graphics in video games, animation, visual effects for film and TV, and even graphical interfaces in apps and on websites. They’re used to simulate things like rain, snow, fog, smoke, fire, water, steam, clouds, swarms of insects, flocks of birds, and so much more. They’re also nice because they require only a small amount of code to make something that looks impressive. Perfect for a lab assignment!

How do you even make a program like this?

If you’ve never made a program like this before, it may look intimidating, but it’s really not. This is training for the first project, where you’ll make a small video game. This lab has all the same parts as a video game, but very simplified.

Real-time interactive programs - including video games, GUI (graphical user interface) apps, web browsers, and just about any other program you use on a daily basis - all work basically the same way:

  1. check for user inputs
  2. respond to those inputs by updating your program state (the variables, data structures, etc.)
  3. change the output (screen) to reflect the new state
  4. wait for a little while
  5. loop back to step 1

You’ve probably done simple games in programming classes before: get inputs from the user, update variables based on game rules, loop back to asking for more input. The difference now is… it’s faster. The player gets 60 turns every second! AAAAAH!


0. Getting started

Right-click and “save link” or “download link” both of these files into the same directory:

Make sure they are really saved as .asm files and not .asm.txt files!

Now open up your lab4.asm and let’s have a look.

What’s in lab4.asm

First it includes lab4_include.asm. Our programs are getting bigger, and it’s not reasonable to put all the code in one file anymore. This is kind of like an import in Java (but it’s more like a #include in C, if you’re familiar with that). You can have a look at lab4_include.asm too if you like!

Then we have a number of constants, the .eqv lines. What these constants are used for will be explained as we need them.

Then there are the variables and arrays. Again, they’ll be explained as needed.

Then there is the main function, which is written for you, though some lines inside the main loop are commented out. You’ll be uncommenting them later.

Finally there is the find_free_particle function. This is just a helper for a function you’ll write later.


The display

In MARS, go to to Tools > Keypad and LED Display Simulator. Not Keyboard and Display MMIO Simulator. This will pop up a window. Click the “Connect to MIPS” button in the bottom left.

     

Once it’s connected, you don’t have to close the window or reconnect it. You can re-assemble and re-run your program as many times as you want while the display is open. I mean, yeah, it might be awkwardly in the way so you can close it, but… I’m just saying you don’t have to close it and reopen it every time you run your program.


How the display works

a box with the top-left labeled DISPLAY_BASE. the top-left is (0, 0); the top-right is (63, 0); the bottom-left is (0, 63); the bottom-right is (63, 63). The address of the bottom-right is DISPLAY_BASE + 1023.


1. Drawing the emitter

Try assembling and running your program. It should assemble and run just fine, but the display will just show a black screen. Boring. But this program runs as an infinite loop! So you need to hit the “stop” button in MARS to make it stop running.

The emitter is the point on the screen from which the particles will be created. If we’re going with a water analogy, the emitter is like the end of the hose where water comes out. The user will control the position of the emitter in this program by using the arrow keys on the keyboard.

In the .data segment of your lab4.asm file, you will see these two variables declared:

	# position of the emitter (which the user has control over)
	emitter_x: .word 32
	emitter_y: .word 5

These are the X and Y coordinates of the emitter.

If we want to see the emitter, we need to draw it to the display. Do the following:

  1. Make a new function called draw_emitter. It will have no arguments and return nothing.
    • Remember what all functions must begin and end with?
  2. Uncomment the jal draw_emitter line in the loop in main.
    • At this point, you can assemble and run your code to make sure nothing has changed. (No news is good news.)
  3. Inside draw_emitter, you’ll do this (please read the stuff after the code before you start):

     display_set_pixel(emitter_x, emitter_y, COLOR_WHITE);
    
    • display_set_pixel is a function that is given to you in the include file. You don’t have to write it.
    • Don’t write li a2, 7. Use the names of constants. li a2, COLOR_WHITE makes much more sense.

This is your goal. Done correctly, when you run your program you should now see this on your screen. If you don’t, something is wrong, and you should fix it before moving on.

Well that’s… exciting? A single white dot. Okay, it’s not that exciting. But now we can make it interactive!


How input works

If you click on the display while your program is running, you can use the arrow keys (up, down, left, right) and the Z, X, C, B keys to interact with it.

When you call the provided input_get_keys_held function, it returns a value where each of those eight keys is represented by one bit, where 1 means the key is held down, and 0 means it isn’t. In otherwords, it returns a bitflag value.

The KEY_ constants near the top of lab4_include.asm exist so you don’t have to remember which bit means which key.


2. Moving the emitter around

Moving the emitter is very simple: if we change the contents of the emitter_x and emitter_y variables, then it will be drawn in a different place on the screen. All we have to do is make it so the arrow keys change the values of these variables.

  1. Make a new function called check_input. It will have no arguments and return nothing.
  2. Uncomment the jal check_input line in the loop in main.
  3. In check_input:
    • call input_get_keys_held (another function from the include file)
    • then write some code that does this (again read the stuff after the code before attempting):
         if((v0 & KEY_L) != 0) {
             if(emitter_x != EMITTER_X_MIN) {
                 emitter_x -= 1; // same as "emitter_x--"
             }
         }
    
    • & is doing a bitwise AND here; the and instruction is what you want. (this is NOT a substitute for &&.)
      • Remember from the bitflags part of lecture that this is “extracting” a single bit from the value in v0 so that we can check if that bit is 0 or not.
    • Do not put the result of the and back into v0 because we’re gonna be doing more checks on it after this code. Use your friend t0 as the destination.
    • You can share one “endif” label for both ifs. But name it like _endif_l: or something since we’re going to have more ifs in this function.
      • (this “nested if” is actually the same thing as && if you think about it!)

Now test it:

If it doesn’t move at all: did you invert the inner if’s condition? How about the outer if? They should both be using beq

If it moves to the left, but doesn’t stop at the left side and goes offscreen: your inner if is probably malformed somehow. Like you branched to the code inside the if.

Something else? Get help.


Now we’ll make it work for all the directions. Repeat that if three more times, changing it like so:

Tips:

Now it should behave like in the animation to the right:

As usual, get help if you can’t get this working.


Tangent: how the particles are represented

In Java, we have classes to represent objects. You can give classes instance fields, and every time you new, you get a new object with its own copies of these fields.

This isn’t really a great match for how assembly works. We’ll be using a different way of representing objects instead. Look up in the .data segment. You’ll see these arrays being declared:

	# parallel arrays of particle properties
	particle_active: .byte 0:MAX_PARTICLES # "boolean" (0 or 1)
	particle_x:      .half 0:MAX_PARTICLES # unsigned
	particle_y:      .half 0:MAX_PARTICLES # unsigned
	particle_vx:     .half 0:MAX_PARTICLES # signed
	particle_vy:     .half 0:MAX_PARTICLES # signed

(The syntax 0:MAX_PARTICLES means “make an array MAX_PARTICLES items long, and fill it with 0s”. It would be like doing new short[MAX_PARTICLES] or so in Java.)

All of the particle_ arrays are parallel arrays. They’re all the same length (MAX_PARTICLES). An “object” is just the set of values with the same index. For example, particle 0’s active field is particle_active[0]; its position is particle_x[0] and particle_y[0]; and its velocity vector is particle_vx[0] and particle_vy[0].

The particle_active array is an array of “booleans” (bytes, but only ever the values 0 for false or 1 for true). All the particles start off inactive (0). When we need a new particle, we search this array for an inactive particle. Then we can mark it active (1), fill in its other properties, and the particle is now “alive.” Later, to “get rid of” a particle, we just set its particle_active back to 0.

A downside to this system is that we have a fixed maximum number of particles available. We can never have more than MAX_PARTICLES particles at one time. But the upside is it’s extremely simple to implement, and is actually very fast.


3.1. Spawning particles

Now things are going to get more interesting. What we want to happen is when the user presses the B key, a new particle will “spawn” (be created) at the emitter’s position. Here’s how:

  1. At the end of check_input, add another if to check if the user is pressing KEY_B, and if so, call spawn_particle.
  2. Make the spawn_particle function.
  3. In spawn_particle:
    1. call find_free_particle.
      • This is the function that searches for an inactive particle. It returns -1 to mean “all particles are active.” Otherwise, the number it returns is an index into the particle_ arrays that is currently inactive.
    2. Move the return value into s0.
      • What do you have to do to “ask for permission” to use s0 in spawn_particle? Do that.
    3. If s0 is not -1:
      1. Set particle_active[s0] to 1. The constant 1. (This marks it as active.)
        • What type of array is particle_active?
        • If you didn’t get familiar with the “short” form of array indexing on lab 3, now’s a great time.
      2. Print out s0 followed by a space, for testing.
        • print_str is in the include file, so you can do print_str " "

Now to test:

You should see the following print in the MARS messages pane:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99

That is, it prints the numbers 0 through 99, and then stops after 99.

If you get an alignment error, wrong kinda store bucko

If you get the output 16 16 16..., you didn’t move s0 into the argument register before doing the print syscall

If you get the output 0 1 1 1 1..., there are a few possible issues:


Tangent: we need more digits!

If we represented the particle positions and velocities using the same coordinate system as the emitter (X and Y both in the range [0, 63]), we would run into a problem: we don’t have enough precision for smooth movement.

If we want our particles to behave something like real physical objects, we need to be able to keep track of fractions of a pixel. Fractions usually mean we have to reach for floats, but there’s another technique called fixed-point numbers.

We might not have talked about this in class yet, but the idea is simple: we use an integer, but we pretend that some places are fractional. For this lab, we scale all the numbers up so that we have more digits of precision. For our particles, we’ll be using a scale of 100 to give us 2 digits of precision after the decimal point.

Instead of the particle coordinates ranging from 0 to 63, they will range from 0 to 6399. 6399 represents “63.99” in our coordinate system. We perform all calculations on this larger range, and then when we draw the particles, we divide by 100 to truncate off the “decimal places”: 6399 / 100 == 63.

So that’s what’s going on with the weird numbers in the PARTICLE_X_MAX/PARTICLE_Y_MAX constants, and why we’re multiplying and dividing by 100 in the upcoming code!


3.2. Finishing spawn_particle

Okay, let’s finish this. In spawn_particle, delete the printing code now that we’ve confirmed it works. But leave the code that sets particle_active!

After setting particle_active, you need to set the other particle variables. Since all the other variables are .half, you can multiply s0 by… what? (How many bytes is a .half?) before accessing the other arrays.

Then set the properties as follows (remember, they’re .half arrays):

Now if you assemble, run, click the display, and hold down B… nothing should happen. Well, nothing visible should happen. Importantly, it should not crash!

If you’re wondering why the heck we’re using s0, it’s cause we’ll need it for a later step.


4. Where the heck are the particles?

We’re creating them, they’re just not visible yet. Just like we had to write draw_emitter, we have to draw the particles too. So:

  1. Uncomment the jal draw_particles line in the loop in main.
  2. Make the draw_particles function. You’re gonna need s0 in this function too.
  3. draw_particles should do this:
for(int i = 0; i < MAX_PARTICLES; i++) { // s0 is i
    if(particle_active[i] != 0) {
        // particle_x and particle_y are UNSIGNED arrays.
        // So which should you use: lh or lhu?
        display_set_pixel(particle_x[i] / 100, particle_y[i] / 100, COLOR_BLUE);
    }
}

None of this code is new to you. You’ve done all this stuff before. Just remember to translate from the outside in. Write the for loop first, then put the entire if inside it. Do it!

I said, put the entire if inside the for loop. That means that you just skip inactive particles. you do not break out of the loop when you find an inactive particle.

Done correctly, you should now be able to “draw” on the screen by moving the emitter around and tapping or holding B to place particles. The animation on the right shows this. You will eventually “run out of particles” to draw with, though, because you only have 100 of them. This is normal.

You’re actually pretty close to being done!


5. Moving the particles

Here’s where the magic really happens. Once the particles start moving, it looks really cool.

  1. Uncomment the jal update_particles line in the loop in main.
  2. Make the update_particles function. Yep, you need s0 here too.
  3. Loop over all the particles, and only for the active ones (just like you did in draw_particles, I mean, it’s literally the same for loop and if inside):
    1. particle_vy[i] += GRAVITY
      • particle_vx and particle_vy are SIGNED, so which do you use, lh or lhu?
    2. particle_x[i] += particle_vx[i] (watch your use of lh vs lhu on these)
    3. particle_y[i] += particle_vy[i] (ditto)
    4. Finally, if the particle’s position is “offscreen” (its X position is less than PARTICLE_X_MIN or greater than PARTICLE_X_MAX or its Y position is less than PARTICLE_Y_MIN or greater than PARTICLE_Y_MAX):
      • set particle_active[i] = 0 to “despawn” it
      • then this particle can be recycled in the future!

I believe in you. You can do this. Once done, you should be able to produce an infinite stream of particles that fall from the emitter, as shown in the animation to the right.


6. Randomizing velocity (making them “spray”)

Now for a little “victory lap.” This step is very quick and just makes things look a little better.

spawn_particle currently starts every particle with a velocity vector of (0, 0) (sitting still). This makes them fall straight down. But if we give each particle an initial “kick” in some random direction, we can make it look like they’re “spraying” out of the emitter instead.

We’re going to use syscall 42 to generate some random numbers. It works like this:

So here’s what you do in spawn_particle:

Doing this may reveal problems in your logic for detecting when particles should despawn off the left/right sides. So you may have to go back to update_particles to fix that.

And that’s it.


Submitting

To submit:

  1. On Canvas, go to “Assignments” and click on this lab.
  2. Click “Start Assignment.”
  3. Under “File Upload,” click the “Browse” button and choose your .asm file.
  4. Click “Submit Assignment.”

If you need to resubmit, that’s fine, just click “New Attempt” on the assignment page and upload it again.


Going Further

None of this stuff is required. It’s just fun ideas if you want to go further.

If you had fun making this, the cool thing about particle systems is how easy it is to change their behavior! Here are some ideas of things you could do: