This week, you’ll be making another interactive program, but with a focus on simulating a large number of objects called particles.

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.


0. Getting started

Here are the starting files (plural) for this lab, as a zip file.

Good lord, what IS all this stuff?

On labs 2 and 3, you were given some very simplified include files that had only what was needed to do those labs. This time, you have been given the entire display driver.

What’s in _lab4.asm

First are some named 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.

Then there is the load_graphics function. You did that stuff on lab 3 already, so having you do it again would be kind of pointless.

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


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, just like lab 3! 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 mouse.

Just like on lab 3 where we had cursor_x and cursor_y, we have emitter_x and emitter_y here. And again, just like lab 3, we need to draw a sprite to see the emitter.

Fortunately, I’ve given you a new function, display_draw_sprite that draws a sprite to the screen for you. All you have to do is call it with 4 arguments. So:

  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:

     display_draw_sprite(emitter_x - 3, emitter_y - 3, EMITTER_TILE, 0x40);
    

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.

If the circle is a little further to the right and down compared to this image, be sure you’re subtracting 3 from the X and Y arguments before calling display_draw_sprite.

If the circle is moving, that means you’re changing emitter_x/y. Don’t. There are no stores in that line of code above.


2. Moving the emitter around

Since we’re using the mouse this time, it’s actually even simpler than lab 3.

There are two variables for getting the mouse position:

If you lw from them, you will get the coordinates of the mouse measured from the top-left of the display. Both X and Y range [0, 127].

However, if the mouse cursor is outside of the display, both variables will instead be -1. (You only have to check one: either they’re both -1 or neither is.)

So now:

  1. Uncomment the jal check_input and make an empty check_input function.
  2. In check_input:
    • If display_mouse_x is -1, return from the function. (That means, branch down to the first pop instruction.)
    • Set emitter_x = display_mouse_x.
    • Set emitter_y = display_mouse_y.

That’s literally it!

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

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

Now the emitter is doing what it needs to; time to make some particles!


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 # signed
    particle_y:      .half 0:MAX_PARTICLES # signed
    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 clicks the mouse, a new particle will “spawn” (be created) at the emitter’s position.

Checking mouse buttons

Mouse buttons work a little differently from keyboard input. There is a display_mouse_held variable, but you never store into it - you only load from it, and it gives you the state of all three mouse buttons but packed into a single integer.

This is a technique known as bitsets or bitflags that we will learn about a little after the exam, but all you need to know about how to check for a button is:

    lw t0, display_mouse_held # get the state of *all* buttons
    and t0, t0, MOUSE_LBUTTON # use bitwise AND to extract just the left button
    beq t0, 0, _endif         # 0 means not held, not-0 means held
        # here, the left mouse button is held. respond to it!
    _endif:

Spawning a particle

  1. In check_input after setting the emitter_x/y variables, you’ll make an if to check if the user is holding MOUSE_LBUTTON (see above), 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?
        • So what type of store will you use? And do you even need to multiply the index?
      2. Print out s0 followed by a space, for testing.
        • You have to move a0, s0 then do syscall 1 to print it out.
        • print_str is in the include file, so you can do print_str " "

Now to test: assemble, run, and click on the display. As long as you hold the mouse button down, it should print numbers like this, stopping after it prints 99:

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

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, 127]), 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.

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 127, they will range from 0 to 12799. 12799 represents “127.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”: 12799 / 100 == 127.

So that’s what’s going on with the weird numbers in the PARTICLE_X/Y_MIN/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 arrays 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, and hold down the mouse button… nothing should happen. Well, nothing visible should happen. Importantly, it should not crash!

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


4. Where 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 SIGNED arrays.
        // So which should you use: lh or lhu?
        display_draw_sprite(particle_x[i] / 100 - 7, particle_y[i] / 100 - 7, 161, 0x88);
    }
}

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, then put the call inside that. 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 display by clicking and dragging. 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!


Tangent: Apples??!???!

Yes, apples. Cause it’s fall. :)

The 161 argument you passed to display_draw_sprite identifies which tile to use. Here is a diagram of all the possible fruit graphics. Go ahead and pick the fruit that speaks to you by changing that argument.

The other “mystery” argument, 0x88, is the sprite’s flags. Try changing the first hex digit - the colors of the fruits get all messed up. Changing the second hex digit will have even weirder effects. Just leave it at 0x88 when you’re done playing around though.


5. Moving the particles

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

Logical OR ||

In Java we can just write two conditions with || to check them both and do something if either one is true. We don’t have that in asm. We have to build it out of branches.

Let’s say I want to do something like if(x == 5 || x == 10). I’d write it like a weird switch-case:

    lw t0, x
    # it's like you're checking for two cases...
    beq t0, 5, _then
    beq t0, 10, _then
    j _endif # and then you have a default that goes to the endif.
    _then:
        print_str "it's either 5 or 10!\n"
    _endif:

Making the particles fall

  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]
    3. particle_y[i] += particle_vy[i]
    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/top sides. So you may have to go back to update_particles to fix that.

And that’s it.


But how’d you get the blue background? (You don’t have to do this)

In main, after jal load_graphics, I did this:

    li t0, 0x4287F5
    sw t0, display_palette_ram

You’ll notice a few references to “palette” in the code. The palette is the set of colors that we can use for the display graphics. Palette entry 0 is used as the background color. So, whatever color you store there shows up as the background.

Google “color picker” and it will give you a color picker. Then you can copy the hex value and paste it in your program using the code above, changing # to 0x. Pick a color that compliments your fruit ;o


Submitting

Upload only your _lab4.asm file to Gradescope once it’s open.


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: