11: Putting it all together
OK, if you've been through the showcase of previous demos then you've seen my demo in amongst them, my girlfriend's name made out of triangles spinning around past a starfield effect.  If you've been doing the homework questions up to now, you should already be able to recreate that exact demo, but I'm going to take you through the process here...

Memory Map
Probably one of the most important things to do when writing a VU demo is to keep track of what you're doing with each QWord of memory.  You can do this on paper if you like, but I find Excel comes in handy, as you can give yourself a worksheet allocating each row to 1 QWord of memory, and using 4 columns to denote the four fields, x, y, z and w.  However you choose to do it, it'll come in handy a lot, so make sure you have some way of keeping track.

My memory map is going to be very simple.  All of my graphics are done in 1 big GIFpacket, split into 2 GIFsegments, one for the logo and one for the starfield.  Thus, I have a GIFtag in QW0, followed by the RGBAQ and XYZ2 data for my logo, followed by a GIFtag for the starfield, and finally RGBAQ and XYZ2 data for each star.  My logo is made of 14 triangles, so that's 42 vertices, each of which needing 2 QWords of memory.  Therefore my logo data takes up QW0 to QW84.  After that I have my starfield GIFtag in QW85, followed by 200 stars, so that takes up QW85 to QW485.  For the moment, I'm not using QW486 to QW1022, but don't forget the data supplied by the harness in QW1023!  That QWord is not available for public use.  To summarise:

QWord Usage
0 Logo GIFtag
1 Colour of vertex 1
2 Position of vertex 1
... Vertex Data
84 Position of last vertex
85 Starfield GIFtag
86 Colour of Vertex 1
87 Position of Vertex 1
... Vertex Data
485 Position of last vertex
486 Unallocated
... Unallocated
1022 Unallocated
1023 Harness Data


So, let's write a gen_data.c to generate the required data.  First up is the GIFtag for the logo.  I decided to create the logo by drawing it out on some graph paper, labelling the centre of the logo as (0,0) and working out the co-ordinates of each of the vertices from there.  I gave the logo an arbitrary constant Z-value of 100, which I'll come back to later.

The definition of the logo then looks like this:

#define NUM_TRIS    14

const int vert[NUM_TRIS * 6] = {    -150,  -90, -150,  -50, -240, -100,
                            -150,  -90, -150,  -50, -220,  -20,
                            -240, -100, -200, -100, -240,  100,
                            -140,  100, -100,  100, -140, -100,
                            -140,  100, -140,   60,  -50,  100,
                             -50, -100,  -50,  -60, -140, -100,
                             -90, -100,  -50, -100,  -50,  100,
                             -40,  100,    0,  100,  -40, -100,
                             -40,  100,  -40,  -40,   50,  100,
                              60,  100,  100,  100,   60, -100,
                              60,  100,   60,   60,  150,  100,
                             190,  -20,  220,    0,  230, -100,
                             180,  100,  220,  100,  200,  -40,
                             180,    0,  210,  -20,  160, -100};

And I fill up the GIFtag with code like this: (after the gifpacket_init of course)

    prim=GS_SET_PRIM(GS_PRIM_TRI, GOURAUD(1), TEXTURED(0), FOG(0), ABE(0), ALPHA(0), AA(0), CTXT(0), FIX(0));
    gifpacket_addgsdata(&gifpacket, GIF_SET_TAG(NLOOP(NUM_TRIS*3), EOP(0), PRE(1), prim, GIF_PACKED, NREG(2)));
    gifpacket_addgsdata(&gifpacket, 0x51);

    for(i=0, j=0; i<NUM_TRIS; i++)
    {
        color.ui32[0]=0x0;
        color.ui32[1]=0xFF;
        color.ui32[2]=0xFF;
        color.ui32[3]=0x80;
        gifpacket_addgspacked(&gifpacket, color.ul128);

        vertex.ui32[0]=(2048+vert[j++])<<4;
        vertex.ui32[1]=(2048+vert[j++])<<4;
        vertex.ui32[2]=100;
        vertex.ui32[3]=1;
        gifpacket_addgspacked(&gifpacket, vertex.ul128);

        color.ui32[0]=0xFF;
        color.ui32[1]=0x0;
        color.ui32[2]=0xFF;
        color.ui32[3]=0x80;
        gifpacket_addgspacked(&gifpacket, color.ul128);

        vertex.ui32[0]=(2048+vert[j++])<<4;
        vertex.ui32[1]=(2048+vert[j++])<<4;
        vertex.ui32[2]=100;
        vertex.ui32[3]=1;
        gifpacket_addgspacked(&gifpacket, vertex.ul128);

        color.ui32[0]=0xFF;
        color.ui32[1]=0xFF;
        color.ui32[2]=0x0;
        color.ui32[3]=0x80;
        gifpacket_addgspacked(&gifpacket, color.ul128);

        vertex.ui32[0]=(2048+vert[j++])<<4;
        vertex.ui32[1]=(2048+vert[j++])<<4;
        vertex.ui32[2]=100;
        vertex.ui32[3]=1;
        gifpacket_addgspacked(&gifpacket, vertex.ul128);
    }

I deliberately set EOP=0 in this GIFtag as I knew I wanted the starfield information to follow immediately afterwards.

Notice that I've decided not to make colour information part of my logo, I've defined each triangle as a Gouraud shaded mix of the 3 secondary colours.  That suits me fine, but you may want to create an array of colour information and feed that into the loop instead, giving you more control over the look of the logo.  In fact, if you want to, you could design a logo generation program which lets you draw the logo you want, and outputs the vertex information into a file for you, so you don't even have to bother with graph paper.  The choice is yours...

Next follows the starfield GIFpacket.  I decided that random (x,y) co-ordinates  and colours would be fine, and I decided that a quick trick will make the movement of the stars easier.  My idea was to store the speed that I wanted the star to move in the Z co-ordinate of the vertex, and use VU code to add the X and Z co-ordinates together each frame.  Then, I would detect when the star reaches the right edge of the screen, and warp it back to the left edge when it does.

Knowing this, I wrote out the starfield GIFtag like this:

    prim=GS_SET_PRIM(GS_PRIM_POINT, GOURAUD(0), TEXTURED(0), FOG(0), ABE(0), ALPHA(0), AA(0), CTXT(0), FIX(0));
    gifpacket_addgsdata(&gifpacket, GIF_SET_TAG(NLOOP(NUM_STARS), EOP(1), PRE(1), prim, GIF_PACKED, NREG(2)));
    gifpacket_addgsdata(&gifpacket, 0x51);

    for (i=0; i<NUM_STARS; i++)
    {
        color.ui32[0]=i+50;
        color.ui32[1]=2*i+50;
        color.ui32[2]=3*i+50;
        color.ui32[3]=0x80;
        gifpacket_addgspacked(&gifpacket, color.ul128);
    
        vertex.ui32[0]=((rand()%640) + 1728) << 4;
        vertex.ui32[1]=((rand()%224) + 1936) << 4;
        vertex.ui32[2]=((rand()%5) + 1) << 4;
        vertex.ui32[3]=1;
        gifpacket_addgspacked(&gifpacket, vertex.ul128);
    }

As you can see, the code gives each star a random x co-ordinate between 1728 and 2367, a random y co-ordinate between 1936 and 2159, and a random z co-ordinate between 1 and 5.  All co-ordinates are shifted 4 times to the right (I didn't need to shift the Z co-ordinate, but by doing so it makes the arithmetic in the VU code easier).  This spreads the stars out randomly on a 640x224 screen centred at (2048, 2048) as required.  By allowing the Z co-ordinate to vary between 1 and 5, we get 5 different layers of stars, creating a nice parallax effect.  However, to ensure the logo always appears in front of the stars, making the stars appear as if they are in the background, I had to make sure the Z co-ordinates of the logo vertices are always bigger than (5 << 4) = 80.  That's why I set them all to 100, a nice round number :)

That's it for the gen_data.c, for the moment at least ;)  You can find the complete version at my project page.  Download it, and run it with the ordinary basic.vcl, which as you know just sends the GIFtag stored at address 0, and you should see a static logo spelling my girlfriend's name in front of an array of dimly coloured stars.  Excellent, we're half way there.

Moving the stars
OK, so now that you know my little trick for moving the stars along, it all seems a little easy.  For every vertex, we load up the X and Z co-ordinates, add them together, check we haven't been past the end of the screen, and store the X co-ordinate back.  That sounds easy enough, let's write the code!

We're going to need a for loop, with a single if statement and I'm going to construct it like this:

for (iPointer = Start of Star data; iPointer != iEndPointer; i+=2)
{
    load iPointer[X] into iX;
    load iPointer[Z] into iZ;
    iX = iX + iZ;
    if (iX > Right edge of screen)
    {
        iX = Left edge of screen;
    }
    store iX into iPointer[X];
}

3 Things need pointing out here, firstly that the edge of the screen is at X co-ordinate (2048+320) = 2368, but we must remember that the number we're dealing with has already been left-shifted 4 times, which is the same as multiplication by 16.  2368 * 16 = 37888.  A similar calculation gives you the left edge of the screen as 27648.  Secondly, as you'd know if you'd read both lessons 6 and 8, although we'd like to check if iX > 37888, we really have to check if iX - 37888 > 0.  However, we can't put 37888 into the isubiu instruction, because it's too big, so really we check if iX - 27888 - 10000 > 0, or any other equivalent sum.

Thirdly, I snuck i+=2 into the for loop, but why?  Well, we're only dealing with vertex XYZ2 co-ordinates, so we have to remember to skip over the RQBAQ data in-between.  Let's see if you can see how the pseudo code translates into VU code.

kStarsStart .assign 85 ; Now you see why the memory map comes in useful :)
kStarsEnd .assign 485

START:
    iaddiu iPointer, vi00, kStarsStart ; Initialise iPointer
    iaddiu iEndPointer, vi00, kStarsEnd ; Initialise iEndPointer
STARSLOOP:
    iaddiu iPointer, iPointer, 2  ; Move onto the (first|next) vertex XYZ data
    ilw.x iX, 0(iPointer) ; Load iPointer[x] into iX
    ilw.z iZ, 0(iPointer) ; Load iPointer[z] into iZ
    iadd iX, iX, iZ       ; iX = iX + iZ
    isubiu iCheck, iX, 27888
    isubiu iCheck, iCheck, 10000
    ibgtz iCheck, WRAP:   ; if (iX - 27888 - 10000 > 0) <Right edge of screen> goto WRAP
WRAPDONE:
    isw.x iX, 0(iPointer) ; Store iX into iPointer[x]
    ibne iPointer, iEndPointer, STARSLOOP  ; Restart the loop if we're not at the end yet
    b KICK:
WRAP:
    iaddiu iX, vi00, 27648 ; iX = 27648 <Left edge of screen>.
    b WRAPDONE:
KICK:
    xgkick vi00 ; Send the whole GIFtag off (Logo and Starfield)

It's important to realise that there are other ways of doing exactly what I just did, it's up to you how you do things, as long as they have the desired effect.  For this reason, I'm going to let you code the next part...

Moving the logo
This is your domain.  I'm going to tell you what to code, you decide how to code it.  Lesson 7 on some Maths may come in useful, vcl_sml.i may come in useful, you decide what you want to do.

What I want to end up with is a spinning logo, that zooms in or out.  Here's my hint - use gen_data.c to pre calculate a matrix that rotates a vertex by 1 degree.  In your VU code, use this matrix to multiply each vertex of the logo, looping through like the above starfield code.  As for the zooming, the method is completely up to you.

Introducing a tiny bit of interactivity.
In my demo, mainly for debugging purposes, but also to teach myself how to add some interactivity to demos, I implemented a small button check.  It went something a little like this:

if (Circle button is not pressed)
{
    Update the starfield
}
if (Cross button is not pressed)
{
    Update the logo
}

To give you a taster, here's how I did the Circle button check, you can easily modify it to check the Cross button if you've read 8: What's in that last QWord?.

kCircle .assign 0x2000

START:
    ilw.w iPad, 1023(vi00)
    iaddiu iMask, vi00, kCircle
    iand iCheck, iPad, iMask
    ibne iCheck, vi00, SKIP_STARFIELD:
STARSLOOP:
    .
    . See above code
    .
SKIP_STARFIELD:    

And that's it, my first ever VU demo, laid bare for all to see!  Next up I'll try to dissect Plasmaroids and show you how that was done, but you'll probably have a good idea yourself by now :)  The main reason I'm choosing Ben's demo is that it too was built up from the basic sample, using the same ideas.  Generate what GIFtags you need, i.e. for the asteroids, the ship, the plasma background, the particles for explosions and acceleration, and the explosions themselves, then alter them with the VU code as you see fit.

If you head over to my project page, you'll find gen_data.c and basic.vcl files for the code given to you in this chapter, ready for you to add the necessary other parts, or rewrite into your own demo!

Happy microcoding!

Prev - 10: Texture Mapping Next - 12: Plasmaroids