Archive for October, 2011

Creating a Slideshow Viewer, Part 2: Reading the Inventory

Sunday, October 16th, 2011

This is the second installment in my small series about creating a slideshow viewer (based on my new texture vendors). In this one, I’ll be showing you how to add images to the viewer.

In my original texture vendors, the prim which displayed the images was responsible for creating a list of the images available, and then of moving through the images when it received a message from one of the buttons.

In the new version, all of this is handled by the Frame prim, which looks for touch events and determines which button was ‘clicked’, and which then updates the texture on the Display prim. This is made possible by another relatively new script function, llSetLinkTexture(), which allows the texture of a prim to be assigned by another (linked) prim.

There are two elements to handling the images. The simplest is assigning a specified texture to the Display prim. However, in order to do this the Frame needs to have the textures stored in it, and needs to be able to make a list of the available textures.

Let’s start by creating this list. First, edit the Frame prim. As the Frame prim and the Display prim are linked, click the ‘Edit linked’ check box on the build dialog, then click on the Frame. Now switch to the contents tab, and drop your images (textures or snapshots) into it.

Now we need to write a script that will create a list of those images. If you have been following along, you will already have a script in the Frame prim, to handle the buttons. Update this to the following code:

    // A list to hold the image keys
    list images = [];
    readImages()
    {
        // Count the number of textures
        integer count = llGetInventoryNumber(INVENTORY_TEXTURE);
        integer i;
        string name;
        images = [];
        // Look through all the texture
        for (i = 0; i < count; i++)
        {
            name = llGetInventoryName(INVENTORY_TEXTURE, i);
            // Get the key, and add it it to the list
            images += [llGetInventoryKey(name)];
        }
        // If we have at least one image, display the first one.
        if (llGetListLength(images) > 0)
        {
            llSetLinkTexture(LINK_ROOT, llList2Key(images, 0), 0);
        }
    }
    float btn_Cols = 8.0;
    float btn_Rows = 8.0;
    integer btn_Id(vector pos)
    {
        integer button = (btn_Row(pos) * (integer)btn_Cols) + btn_Col(pos);
        return button;
    }
    integer btn_Row(vector pos)
    {
        // Flip the y-axis, so that it runs from top to bottom.
        float y = 1.0 - pos.y;
        integer row = (llFloor((y * 10) * btn_Rows) / 10);
        return row;
    }
    integer btn_Col(vector pos)
    {
        integer col = (llFloor((pos.x * 10) * btn_Cols) / 10);
        return col;
    }
    default
    {
        state_entry()
        {
            readImages();
        }

        touch_end(integer count)
        {
            vector mousePos = llDetectedTouchST(0);
            integer id = btn_Id(mousePos);
            llOwnerSay("Button " + (string)id + " pressed");
        }
    }
    

This finds all the texture items in the prim’s contents, and stores their names in the Image list. Once that is done, it gets the first image from the list, and assigns it on the Display prim.

The llSetLinkTexture() function is used to display the image. The allows us to set the texture of another prim, in this case the display prim, by specifying the prim number, the UUID of the texture (which we read from our list), and the face, which is here assumed to be face 0.

One thing to be careful about here is that it assumes that the Display prim is prim 0, that is, the root prim. If you don’t already know how to do this, the trick is that when you select the prims that you want to link, the root prim should be the last prim that you select before linking. In this case there are only two prims, so select the Frame prim first, then Shift+Click the Display prim to add it to the selection. Finally link the prims (the option to link is on the Build menu in Viewer 2, and in most third-party viewers).

Creating a Slideshow Viewer, Part 1: Buttons

Tuesday, October 11th, 2011

Recently I decided to update my Texture Vendors. If you have seen my stores inworld, you will know that the Texture Vendors display one texture at a time, with buttons to let you cycle through the different textures in the set.

The current version of the vendors was created quite a while ago, and uses techniques that are now out-dated and inefficient. Each vendor uses three scripts — one for each button, and a script in the central prim to actually display the texture. Messages are sent from the script to the central prim to tell it to change textures.

With some of the new features that have appeared over the last couple of years it should now be possible to reduce those three scripts into one much more efficient script, and that’s what I am planning on doing.

While I am doing it, I am also going to write it up in this blog, because the basic idea of a gadget which lets you move forwards and backwards through a set of images obviously has more uses than just as a texture vendor.

The slideshow needs at least two buttons, to move forwards and backwards through the slides. It might need ‘first’ and ‘last’ buttons as well.

Until a couple of years ago the only reasonable way to implement buttons was to create a prim for each button, with a script in each prim to detect a touch event, and probably then to send a message to some kind of controller. This is the way my existing vendors work, and you can see that this is hardly efficient for either prims or scripts.

In Viewer 1.21, however, the llDetectedTouchST() function was introduced (along with some other related and useful functions). This allows you to detect which point on a prim face was touched. This makes it possible to have several buttons as a single texture, and then determine which button was actually clicked on.

Second Life Wiki: “Detected” functions

To test this out, and create a starting point for the viewer, I’m going to create two prims. One will be the surface on which the images will be displayed, and the other will be the surrounding frame, which will include any buttons.

Create two prims, one with a size of 1.0 x 1.0 x 0.1, which will be the frame, and the other with a size of 0.75, 0.75, x 0.1, which will be the display face. For the frame, set it to hollow, at a value of 80, and rotate both prims so that they are facing the right way. Centre the display prim inside the frame prim. Given those values, you should find that the prims fit together exactly. Set the textures to blank.

If you want a larger version, try setting the frame to 2.0 x 2.0 x 0.1, hollowed to 75, and the display frame to 1.5 x 1.5 x 0.1.

To test the buttons, I’m going to use a grid texture. I’ll assign it by UUID using a temporary script, so that you can use the same texture. In the Frame prim, create the following script. You might need to change the ‘face’ parameter to get the image onto the correct side of the prim. Once the texture has been applied, you can delete the script again:

    default
    {
        state_entry()
        {
            integer face = 0;
            llSetTexture("53e5f19a-70a0-ae65-831f-da5f4ec69fdb", face);
        }
    }
    

You should end up with something like this (I’ve coloured the display prim for clarity, but actually you should leave it white):

Blog tutmisc 0002a

We now have an 8×8 grid, of which the outermost cells are visible. Now we can test the button-handling. To work out which cell was clicked on requires a little bit of calculation, based on the size of the grid. The code I’m using is relatively generic, so you can modify it for different button layouts simply by changing the ‘btn_Rows’ and ‘btn_Cols’ values.

Create a new script in the Frame prim, and copy in the following code:

    float btn_Cols = 8.0;
    float btn_Rows = 8.0;
    integer btn_Id(vector pos)
    {
        integer button = (btn_Row(pos) * (integer)btn_Cols) + btn_Col(pos);
        return button;
    }
    integer btn_Row(vector pos)
    {
        // Flip the y-axis, so that it runs from top to bottom.
        float y = 1.0 - pos.y;
        integer row = (llFloor((y * 10) * btn_Rows) / 10);
        return row;
    }
    integer btn_Col(vector pos)
    {
        integer col = (llFloor((pos.x * 10) * btn_Cols) / 10);
        return col;
    }
    default
    {
        touch_end(integer count)
        {
            vector mousePos = llDetectedTouchST(0);
            integer id = btn_Id(mousePos);
            llOwnerSay("Button " + (string)id + " pressed");
        }
    }

Now try clicking on the grid. As you do so, it should display (in local chat) the number of the cell that you clicked. The top-left cell should be 0, and the bottom-right cell should be 63. You might need to rotate the prim if this is not the case, although as long as you know which cell is where, you don’t necessarily need to do that. It’s just more convenient.

Aside: Why am I using touch_end() instead of touch_start()? This is because if you use touch_start() to trigger a state change, the touch_end gets ‘lost’ (it’s expecting the wrong script state), which can cause problems, and result in touches not being detected. Using touch_end() is safer. If you know that you are not going to change state, you can use touch_start().