Discovering Texture Offsets

Joshewa Daniels asked about a way to display a desired number from an array of numbers in a texture. This means, of course, that one needs to calculate the offsets and scales. I don’t really know how to do that, but I know how to figure it out. Here’s how I do it.

Joshewa has a texture, apparently, with 1-10 in the first row, 11-20 in the second, and 91-00 in the tenth. He would not loan me a copy, so I am working, instead, with a 10×10 texture that looks like this:

I’ve put this on a prim in which I’ll build my indexing script. It looks like this:

The texture offsets are 0.00, and the texture repeats per face are 1.0. Clearly since there are ten rows and ten columns, we want the repeats per face to be 0.1. With that change, it looks like this:

We see now that we are looking at the center of the texture. So SL textures 0,0 location is the center of the texture. One would have hoped for top left in this case, but no matter. Let us reason together … to get the a01 location, the top left of our original texture, to appear on the prim, we need its center to be at the center of the prim. The a01 is in the zeroth row, zeroth column of the texture as we would think of it. But the prim’s 0,0 is looking at 0.5, 0.5.  First, let’s get the e05, now in the top left corner of our prim, to fully appear. To do that we need to move a little to the left, and a little bit up. Since each cell is 0.1 wide and high, we need to move by 0.05 in each direction … half of 0.1. We should reduce x by 0.05, and increase y by 0.05, as I see it. Sure enough, that works:

Great. Now let’s see. The e05 cell is the 4,4 cell (zero-based) as seen from the top left. And its offset is -0.05x, +0.05y. So how can we get that a01 to show up? (The 0,0 cell as we look at it from the top left.) That cell is 4 squares up, and 4 squares left. Each square is 0.1 in each dimension. To go up, we add to y. To go left, we subtract from x. So we want -4.1 added to x, and 4.1 added to y. So x should be -0.45 and y should be 0.45. Let’s try that.

It works! I am beginning to understand how texture alignment works. Our 4,4 cell is at -0.05, 0.05, and our 0,0 cell is at -0.45, 0.45. I want to work from 0,0 in the top left to 9,9 in the bottom right, so that will mean that when x is zero we want our offset to be -0.45, and when y is zero, we want our y offset to be 0.45.

Now then, we really want to go forward from 0,0 to n, m, so we know that we really want to add 0.1 to x for every step, and subtract 0.1 from y for every cell. And we’ll add (-0.45, 0.45) to adjust for the center.

So let me write a script that will position the texture to the position cx,cy when I say, oh, “position 1,2” or something like that.

default {
    state_entry() {
        llListen(0, "", llGetOwner(), "");
    }

    listen(integer chan, string name, key id, string msg) {
        if ( llSubStringIndex(msg, "position") != 0 ) return;
        string coords = llGetSubString(msg, 9, 99);
        list xy = llCSV2List(coords);
        float cx = (integer) llList2String(xy, 0);
        float cy = (integer) llList2String(xy, 1);
        float addX = 0.1*cx;
        float addY = 0.1*cy;
        float offsetX = -0.45 + addX;
        float offsetY =  0.45 - addY;
        llOffsetTexture(offsetX, offsetY, ALL_SIDES);
    }
}

This works as planned. Now this is not as tight as it might be. As experienced readers know, I like to break out steps to make them clear to myself, and hopefully, to you. Now that the script works, let’s refactor a bit. First, I’ll extract a function to position the texture to cx, cy, on some side:

positionTexture(float cx, float cy, integer side) {
        float addX = 0.1*cx;
        float addY = 0.1*cy;
        float offsetX = -0.45 + addX;
        float offsetY =  0.45 - addY;
        llOffsetTexture(offsetX, offsetY, side);
}

default {
    state_entry() {
        llListen(0, "", llGetOwner(), "");
    }

    listen(integer chan, string name, key id, string msg) {
        if ( llSubStringIndex(msg, "position") != 0 ) return;
        string coords = llGetSubString(msg, 9, 99);
        list xy = llCSV2List(coords);
        float cx = (integer) llList2String(xy, 0);
        float cy = (integer) llList2String(xy, 1);
        positionTexture(cx, cy, ALL_SIDES);
    }
}

Now this does not solve Joshewa’s whole problem. He actually wants to position the values from 1-99, and zero. In our terms, when he puts in 1, he should see the a01 texture, and when he puts in 99, he should see the J09 texture, and when he puts in 0, he should see the J10 texture.

(Now we see why it would have been better to make the texture go 0 – 9, 10-19, rather than 1-10, 11-20 … but we can’t have everything we want. Anyway, we now need just to take our input value, 0-99, and convert it correctly into a call to our function.)

If we had been lucky enough to have a 0-9 texture (sigh), then the conversion would be to set the desired row to N / 10, and the desired column to N mod 10, or N % 10 in LSL-speak. That is … the row would be the tens digit, and the column would be the units digit, of the input number. As it is, we now have to deal with offsetting the value and adjusting for the wraparound.

I think I’ll do that in two steps. Not that we couldn’t do all of this in one go, but you know I like to go in tiny steps so I can be sure each bit is correct. First, I’ll create a function that, given an integer 0-99, displays the appropriate cell. That is, for 0, it will display the a01 cell, for 10, the b01, and for 99, the j10. Here’s that:

positionTexture(float cx, float cy, integer side) {
        float addX = 0.1*cx;
        float addY = 0.1*cy;
        float offsetX = -0.45 + addX;
        float offsetY =  0.45 - addY;
        llOffsetTexture(offsetX, offsetY, side);
}

default {
    state_entry() {
        llListen(0, "", llGetOwner(), "");
    }

    listen(integer chan, string name, key id, string msg) {
        if ( llSubStringIndex(msg, "position") == 0 ) {
            string coords = llGetSubString(msg, 9, 99);
            list xy = llCSV2List(coords);
            float cx = (integer) llList2String(xy, 0);
            float cy = (integer) llList2String(xy, 1);
            positionTexture(cx, cy, ALL_SIDES);
        } else if ( llSubStringIndex(msg, "value") == 0 ) {
            integer value = (integer) llGetSubString(msg, 5,99);
            positionTexture(value % 10, value / 10, ALL_SIDES);
        }
    }
}

That works a treat. Saying “value 0” displays a01, saying “value 99” displays j10. Now let’s do the adjustment. In Joshewa’s case, he wants value 1 to display a01. And he wants value 99 to display j09. And, he wants value 0 to display J10.

For the first two, it would suffice just to subtract 1 from the input value before passing it into our column and row calculation. But for the third, we need to convert 0 into 99. Let’s do this: Let’s first check for 0, making it 100 and then subtract one. I’ll do this with a third option on my test, so that I can check that it still works after adding this new capability:


positionTexture(float cx, float cy, integer side) {
        float addX = 0.1*cx;
        float addY = 0.1*cy;
        float offsetX = -0.45 + addX;
        float offsetY =  0.45 - addY;
        llOffsetTexture(offsetX, offsetY, side);
}

default {
    state_entry() {
        llListen(0, "", llGetOwner(), "");
    }

    listen(integer chan, string name, key id, string msg) {
        if ( llSubStringIndex(msg, "position") == 0 ) {
            string coords = llGetSubString(msg, 9, 99);
            list xy = llCSV2List(coords);
            float cx = (integer) llList2String(xy, 0);
            float cy = (integer) llList2String(xy, 1);
            positionTexture(cx, cy, ALL_SIDES);
        } else if ( llSubStringIndex(msg, "value") == 0 ) {
            integer value = (integer) llGetSubString(msg, 5,99);
            positionTexture(value % 10, value / 10, ALL_SIDES);
        } else if ( llSubStringIndex(msg, "joshewa") == 0 ) {
            integer value = (integer) llGetSubString(msg, 7,99);
            if ( value == 0 ) value = 100;
            value -= 1;
            positionTexture(value % 10, value / 10, ALL_SIDES);
        }
    }
}

In fact, this works just fine. Now let’s refactor to remove duplication and clean up the code. I think I’ll add a row() and column() function from value to the x and y … and that I’ll provide a function adjust() to convert from the zero-based notion to joshewa’s one-based. The result is this:

positionTexture(float cx, float cy, integer side) {
        float addX = 0.1*cx;
        float addY = 0.1*cy;
        float offsetX = -0.45 + addX;
        float offsetY =  0.45 - addY;
        llOffsetTexture(offsetX, offsetY, side);
}

integer column (integer value ) {
    return value % 10;
}

integer row(integer value ) {
    return value / 10;
}

integer adjust(integer value) {
    if ( value == 0 ) value = 100;
    return value -1;
}

default {
    state_entry() {
        llListen(0, "", llGetOwner(), "");
    }

    listen(integer chan, string name, key id, string msg) {
        if ( llSubStringIndex(msg, "position") == 0 ) {
            string coords = llGetSubString(msg, 9, 99);
            list xy = llCSV2List(coords);
            float cx = (integer) llList2String(xy, 0);
            float cy = (integer) llList2String(xy, 1);
            positionTexture(cx, cy, ALL_SIDES);
        } else if ( llSubStringIndex(msg, "value") == 0 ) {
            integer value = (integer) llGetSubString(msg, 5,99);
            positionTexture( column(value), row(value), ALL_SIDES);
        } else if ( llSubStringIndex(msg, "joshewa") == 0 ) {
            integer value = (integer) llGetSubString(msg, 7,99);
            value = adjust(value);
            positionTexture(column(value), row(value), ALL_SIDES);
        }
    }
}

I think we’ll leave it here. Joshewa, I hope this helps in two ways: First, these functions probably do nearly what you want to do. Second, the article may be helpful in showing you how I think about how to derive such a beast.

Good luck!

Comments are closed.

Blog at WordPress.com.

Up ↑

<span>%d</span> bloggers like this: