Jump to content

Need a script which maps a texture across any specific range of a cube grid


Mircea Lobo
 Share

You are about to reply to a thread that has been inactive for 4323 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

I'm working on a script of unusual complexity, which I can't figure out on my own. I have a grid of cubes perfectly aligned to each other, all linked to a separate master primitive. Those cubes represent chunks of a screen. I want my script to be able to set a texture across any specified range of cubes, while being able to give the texture any rotation. To better understand this, here is an image which represents the effect I want (done manually in edit mode for now):

Untitled.jpg

As you can see, I have a grid of 4 x 4 cubes linked to a master cube (which would contain the script in this case). The texture would be set with llSetLinkPrimitiveParams on each prim, and I already have a for() loop which runs through all the cubes and detects where each belongs. This is done by looping through all child primitives, and for each linknumber obtaining the coordinates as a vector (eg: for top-rightmost one it would be <1, 4, 0>). The problem is generating texture coordinates so each surface is given the repeats and offset necessary for the image to be mapped properly.

Note that my script does not use a constant range of blocks to map over. If we imagine it works via chat command with my example, I could tell the script to map the texture from 1 x 1 to 4 x 4 (meaning all cubes as in my picture), 1 x 1 to 2 x 2 (which would only map it on the first 4 cubes in the top-left corner), 2 x 2 to 3 x 4 (which would be a rectangular area and the texture would need to be stretched 1X2) or 1 x 4 to 1 x 4 so it only maps to that single cube... and of course any other imaginable position. I also have a larger cube grid in my final project. Also, I want this script to accept custom rotations too, so I can rotate my image any way and still have it map as correctly as possible. I assume that rotating a texture 45* across a 2 x 4 cube group would result in some parts poking out, but I'd still like the prettiest result possible. With fixed rotations however (0* 90* 180* etc) I want the texture to fit perfectly across any specified cube range.

Can someone more experienced at maths figure a script that can do this please? I'm using it for something pretty nice, which I plan to make public once it's ready.

Link to comment
Share on other sites

Almost two years ago there was a very good discussion in this forum about a similar problem. By the end of that thread, the OP had figured out how to solve it and  had created a marketable product.  I am trying very hard to remember his name now, and will look through snippets that I may have saved on my home computer this evening.  It's a nice challenge.

Link to comment
Share on other sites

A mathematical representation of your artifact is called a matrix. For the one in your pic it will look like this:

 | 11,12,13,14 |
 | 21,22,23,24 |
 | 31,32,33,34 |
 | 41,42,42,44 |

the first digit is the row number and the 2nd digit is the column number.

You define a sector in the matrix by specifying the starting and ending element. For instance 21-24 means all elements in the second row, while 12-42 means all elements in the second column. Likewise 21-24, 31,32 means all elements in the second row + 2 first elements in the 3rd row... and so on.

When a script knows the number of elements it can calculate the frames according to whatever (to be determined) algorithm.

If want to proceed call me in-world.

 

 

Link to comment
Share on other sites

Thanks. I'll probably do that when I log on the main grid again (I do most of my works on my OpenSim simulator, and port them to SL later if they're good). That matrix representation looks like what I have... but note that most of my script is already done and the loop is already in place.

Like I said, there is a for loop which goes through all of the linkset (only those prims which are cubes part of the matrix). I won't copy my complex code now, but here's something close (a quick mockup to explain my architecture):

// example of how the range is defined between a virtual rectangle consisting of two points
integer start_x = 1;
integer start_y = 1;
integer end_x = 3;
integer end_y = 3;

integer i;
for(i = 0; i <= llGetNumberOfPrims(); i++) // loop through all child primitives
{
    if(is_part_of_matrix(i)) // custom function which returns true if this child is part of the matrix
    {
        vector v = get_position(i); // custom function which gets the X and Y coordinates of this chunk
        llSetLinkPrimitiveParamsFast(i, [PRIM_TEXTURE, bla, bla, bla]); // bla bla bla is where I set the texture coords

        // ^ in the line above I need to set texture repeats and offsets to match each chunk
        // by using vector v and knowing the start and end values via start_x start_y end_x and end_y
    }
}

The actual code is very different than that, but this explains what I have and how it works. It would be hard to change the architecture so I hope it can work in a loop of that sort.

Link to comment
Share on other sites

Well, I didn't have any luck tracking down the old thread that I mentioned yesterday, so it remains as a nagging mystery.  If I have a flash of insight and find it again, I'll post here.  Sorry.

Meanwhile, a small distant voice reminds me that the texturing community has dealt with this challenge for a long time as they try to tile textures across various surfaces. There are a couple of useful grids and a brief discussion of relevant anaytical geometry at http://voidsinger.blogspot.com/2010/04/help-withtexture-mapping.html .

Link to comment
Share on other sites

I saw this thread earlier (I lurk from time to time) and was thinking about it.  I do love a good, solid scripting "problem."

Since you're working with a preexisting linkset as opposed to providing the script blindly (e.g., for someone else to use and/or apply to their own linkset), it seems to me like you can dispense with some of the awkwardness.  You should know upfront the total size of your grid.  And if we assume you're working from a known product, then you can also hardcode the link numbers (which makes everything easier - no discovery needed).

Since lists index from 0 to LIST_SIZE - 1, I redid my notes below to consider indices from 0 to SIZE - 1.  Turns out it made the math easier to parse, too.

I came up with 3 steps, though the first two are more setup than anything:

  1. associate link number (integer LN) with prim identity in matrix;
  2. receive desired image size & rotation; and
  3. show desired image.

For (1), just use a hardcoded list of link numbers per row or column.  By row made sense to me, so I went with:

 

list x0 = [LN00,LN10,LN20,LN30,...,LN(x_TotalLength - 1)0];list x1 = [LN10,LN11,LN21,LN31,...,LN(x_TotalLength - 1)1];
...
list xy = [etc.];

 Note that this is with indexing from 0 to LENGTH - 1.  For ease, assume that 0,0 is the upper left corner.

For (2), that's just a matter of input and can be handled however you like - chat commands, dialogue, notecard reading, HUD, hardcoded, whatever.  For now, let x_Length and y_Length each correspond to the target size of the displayed image.  For example, consider a grid of 3x3.  If you wanted to show the image on the first 2 pixels (prims) of the top row, you have x_Length = 2 and y_Length = 1.

For (3), the fun begins!

 

integer face = 1;      //face number for all prims/pixelsstring texture = "TEXTURE NAME";integer x_Length;integer y_Length;integer x;integer y;

integer LN; //link number of corresponding primfloat ur; //x value for repeatfloat vr; //y value for repeatfloat uo; //x value for offsetfloat vo; //y value for offsetfloat rot = 0.0; //rotation in radians

list x0 = [see above];
list x1 = [see above];
...

//insert code re selecting x_Length & y_Length

ur = 1.0/(float)x_Length; //repeat is the % of image to showvr = 1.0/(float)y_Length; //static for all x and yfor(y = 0;y <= y_Length - 1; ++y) { for(x = 0,x <= x_Length - 1; ++x) { LN = llList2Integer("x"+(string)y,x); uo = (float)x/(float)x_Length; //offset is incremental vo = (float)y/(float)y_Length; //clearly not static for all x and y llSetLinkPrimitiveParamsFast(LN,[PRIM_TEXTURE,face,texture,<ur,vr,0.0>,<uo,vo,0.0>,rot]); }}

 

I haven't tested this out, but it makes sense to me.  I could easily be wrong - admittedly, I'm learning more and more LSL as I go.

As for rotation, I'd have to test it out to be sure but I'd start with the above and then try hardcoding examples with rot = PI/2 and things like that.  You can find examples of how to go from degrees to radians (I have code for it somewhere if need be).

Obviously, the above is more just some dirty figuring to get you moving.  Please let me know if/how it works out!

 

(Edited because I'm learning how to post.) 

Link to comment
Share on other sites

Thanks a lot, that looks like it might help. I plan to release my script, but after the whole thing is ready. And it will work with custom linksets too, you just need to name and describe each primitive accordingly. But I can show some parts of it for more clarification.

I already cache the link numbers at script startup into a list, followed by the vector coordinates of each matrix... which the script gets from each prim's description using llCSV2List() while matrix description is of the form 6,4. Here is that part of the code (original code from my script):

 

// this caches all screen chunks into multiple lists at startup, in order to easily fetch their various properties// each index MUST refer to the the same chunk in all lists, so always add them in the same order!// eg: index 2 in the cache_linknum list and index 2 in the cache_coordinates list must both store the properties of chunk 1 x 3list cache_linknum;
list cache_coordinates;
cache_chunks() // called in state_entry{ cache_linknum = cache_coordinates = []; integer i; for(i = 0; i <= llGetNumberOfPrims(); i++) { list l = llGetLinkPrimitiveParams(i, [PRIM_NAME, PRIM_DESC]); if(llList2String(l, 0) == "Chunk") // this primitive is a chunk { // the description field of each chunk contains X and Y coordinates // we first extract them from the string so we can later put them inside a vector string desc = llList2String(l, 1); list xy = llCSV2List(desc); // separate coordinates by comma integer pos_x = llList2Integer(xy, 0); integer pos_y = llList2Integer(xy, 1); // exception handling if(!pos_x || !pos_y) handle_exception("Incorrect chunk coordinates in the description " + desc + ", please write X and Y position separated by a comma", TRUE); // now write the linkset number and coordinates to the permanent cache lists cache_linknum += [i]; cache_coordinates += [<pos_x, pos_y, 0>]; } }}

 

That's how the matrices are cached at startup, so each one's link number and position can be found at the same index of two lists. This part works well and is finished. Now here's what I have so far from the texture setting code (and the functions using it), which is currently only able to set the texture on each matrix without changing the repeats / offsets. Irrelevant custom functions are omitted.

 

TOTAL_CHUNKS = 192;

// returns the coordinates of a chunk based on the linkset number given as a vector
vector linknum_to_coordinates_vec(integer linknum)
{
    integer i;
    for(i = 0; i <= TOTAL_CHUNKS; i++)
    {
        if(llList2Integer(cache_linknum, i) == linknum)
            return llList2Vector(cache_coordinates, i);
    }

    // exception handling
    handle_exception("No chunk found with the link number " + (string)linknum, FALSE);
    return <0, 0, 0>;
}

// returns TRUE if chunk is located in the given range and FALSE if not// the range is a virtual rectangle stretching between coordinate start and coordinate end, read from left to right and up to downinteger inrange(integer chunk, integer start_x, integer start_y, integer end_x, integer end_y){ vector pos = linknum_to_coordinates_vec(chunk); if(pos.x >= start_x && pos.x <= end_x) if(pos.y >= start_y && pos.y <= end_y) return TRUE; return FALSE;}// sets the specified textue on the display chunks in the given rangetexture_set(string inventory, integer start_x, integer start_y, integer end_x, integer end_y){ integer i; for(i = 0; i <= llGetListLength(cache_linknum); i++) // loop through all cached chunks { integer linknum = llList2Integer(cache_linknum, i); // extract link number from the list index if(inrange(linknum, start_x, start_y, end_x, end_y)) // chunk is in the range specified { // if texture is blank, clear the chunk if(inventory == "") { llSetLinkPrimitiveParamsFast(linknum, [PRIM_TEXTURE, CHUNK_FACE, BLANK, <1, 1, 0>, <0, 0, 0>, 0]); llSetLinkPrimitiveParamsFast(linknum, [PRIM_COLOR, CHUNK_FACE, <1, 1, 1>, 0]); if(DEBUG > 1) llOwnerSay("Cleared texture on the chunk " + linknum_to_coordinates_str(linknum)); } else { llSetLinkPrimitiveParamsFast(linknum, [PRIM_TEXTURE, CHUNK_FACE, inventory, <1, 1, 0>, <0, 0, 0>, 0]); llSetLinkPrimitiveParamsFast(linknum, [PRIM_COLOR, CHUNK_FACE, <1, 1, 1>, 1]); if(DEBUG > 1) llOwnerSay("Assigned texture " + inventory + " to the chunk " + linknum_to_coordinates_str(linknum)); } } }}

 

It happens in the second llSetLinkPrimitiveParamsFast group, which is where I need to get set the stretching / repeating based on the functions and caching I have till then.

Link to comment
Share on other sites

Thanks for the further information!  That certainly helps with some of the specific implementation aspects.

As an initial thought, given that the link numbers will range from 1 to llGetNumberOfPrims(), is there a need to index from 0 in cache_chunks?  Seems to me like use of the index 0 just causes some unnecessary evaluations for a prim that doesn't exist.

Another thought, you use a for loop in linknum_to_coordinates_vec to search through cache_linknum for the corresponding entry.  llListFindList might be a better option.  That way, a single function call returns the index and avoids unnecessary evaluations as otherwise are performed with the for loop.

 

Turning to the problem at hand:

(1)  I assume your "chunks" are the prims/pixels of the display.

(2)  I assume the position information in the description field is just the x, y coordinates of that chunk within the display.

Given that you appear to be working with a large number of chunks (192?!??  Wow!), you may want to avoid iterating over the entire chunk set with the for loop in texture_set.  This would also avoid the inrange function entirely (thus reducing evaluations, speeding things up, etc.).

Such functionality would not entail a serious modification of your existing script since if x and y are integers for the coordinates of the chunk within the display, you can preselect specific chunks by using llListFindList(cache_coordinates,[<x,y,0>] to find the index and then look up the corresponding link number from cache_linknum.

For example, let's use j as the y index and i as the x index:

 

for(j = start_y; j <= end_y; ++j) {    for(i = start_x; i <= end_x; ++i) {        integer k = llListFindList(cache_coordinates, [<i,j,0>]);        integer linknum = llList2Integer(cache_linknum, k);        (do other stuff);    }}

 

I started writing out another block of code for your specific implementation when I realized that I need more information.  It's not super critical since it really only affects offsets and signs and such, but it would annoy me immensely to provide code that doesn't work as desired without further modifications.

It sounds like the matrix indices for a given chunk are stored in the description field of that chunk (for example: "2,5" or "24,93").  Do these indices start at 0 or 1?  Also, which corner of the display is 0,0 or 1,1?  (Top left corner?  Bottom left corner?)

If you give me answers to those two questions, along with any comments or revisions for the other bits, I can give you a specific example of offset & repeat code for your script.  It'll pretty much be what I put in my previous post but with some tweaks for your variable names and specific setup.

Link to comment
Share on other sites

Thanks for those valuable observations! I noticed now that I indeed run the loop more than I should be. I forgot that master is linknumber 0, so I can start the loop from 1 instead (since the master prim will never be a chunk). And I never used llListFindList before, but it sounds like an easier way to do exactly what I already am, so I'll try to switch to that. I'll look in this function's description later.

But yes, the coordinates in the description are exactly the matrix positions written that way. So the top-left prim is 1,1 the prim to its right is 1,2 and the prim under it is 2,1 (don't remember exactly which is X and which is Y, might be the other way around). It indeed starts from top-left and the way I imagined it, prims are counted left to right then up to down (although in practice link numbers can be random for each matrix, since I didn't link them in a specific order).

But yes, I start from 1,1 and the size of the screen in my case is 16 x 12 (hence 192 prims). I initially wanted to make something way larger (512 prims) but ran into SL's limit of 256 prims per linkset, and it was getting slow to render. I know that would have been a lot... but then my screen would have been highres enough to even allow scripted textured fonts. I'm sorta pushing the limits for what I'm doing, but I'm hoping it will be nice and good after it's ready.

Link to comment
Share on other sites

Oh... I forgot something else I want to add; I'd also like to be able to manually specify a scale offset when issuing the mapping command. So for instance, if I select matrixes 1 x 1 to 4 x 4 but: Choose an X = 1 and Y = 1 scale, it will be mapped fully over those chunks (like we discussed till now). And if I choose X = 1 and Y = 2 it will repeat two times in Y, while X = 0.5 and Y = 1 means only half will be mapped in the X direction and so on.

Maybe an offset value can be added as well, so texture offsets can also be bumped to push the texture across all chunks (0 x 0 leaving it unmodified)... but I hope this isn't way more than what can be done. I know this makes it a lot more complicated, but it would help a lot if those extra abilities could be added as well please.

Link to comment
Share on other sites

 If I recall correctly, it's that a non-linkset (i.e., an unlinked single object/prim) has a link number of 0.  Link numbers in a linkset begin from 1 (root prim) and are determined by the order in which the prims are selected for the linkset.  You shouldn't ever find a link number of 0 in a linkset.  (See http://wiki.secondlife.com/wiki/LlGetLinkNumber

Since with LSL we're forced to store information in (multiple) lists, I believe llListFindList is the index location mechanism we're afforded.  I'm currently unaware of another LSL search tool.  I'm sure you can do fun stuff with exporting the information (e.g., to a server, to be operated on in a real coding language) and importing the results, but that's well beyond my abilities.

Top left is 1,1.  Got it.  As for the additional "global" display scale (g_scale_x and g_scale_y) and "global" display offset (g_offset_x and g_offset_y), those are pretty easy since they're really just some extra "offset" values.  Not a problem.  I think this is what the code would look like:

 

float g_scale_x = 1.0;float g_scale_y = 1.0;float g_offset_x = 0.0;float g_offset_y = 0.0;//set g_scale_x and g_scale_y, ranging from 0.1 to 100.0; none/default = 1.0 (see above)//set g_offset_x and g_offset_y, ranging from -1.0 to 1.0; none/default = 0.0 (see above)//make sure all of those values are floatsfloat rot = 0.0;integer size_x = end_x – start_x +1;integer size_y = end_y – start_y +1;float repeat_x = (1.0/(float)size_x)*g_scale_x;float repeat_y = (1.0/(float)size_y)*g_scale_y;for(j = start_y; j <= end_y; ++j) {    for(i = start_x; i <= end_x; ++i) {        integer k = llListFindList(cache_coordinates, [<i,j,0>]);    //change to <j,i,0> if y,x in object description        integer linknum = llList2Integer(cache_linknum, k);        float offset_x = (((float)i – (float)start_x)/(float)size_x) + g_offset_x;        float offset_y = (((float)j – (float)start_y)/(float)size_y) + g_offset_y;        llSetLinkPrimitiveParamsFast(linknum,[PRIM_TEXTURE,CHUNK_FACE,inventory,<repeat_x,repeat_y,0.0>,<offset_x,offset_y,0.0>,rot]);    }}

 

I purposefully just added the global scale and global offset bits to the code I wrote earlier today.  That way someone could remove them if they didn't want that feature (slightly more modular approach - don't think it costs much if anything).

You can add in the other bits - if the texture is blank, debugging text, etc.

As an fyi, once the 192 chunk version is working, in theory you can extend the functionality to additional linksets by having the linksets communicate with one another.  That would be one way to reach your 512 prim version.  I don't know too much about such communication (haven't done it yet myself), but I remember looking at it and jotting down some notes somewhere.  If you do want to pursue something crazy like that (hehe), I'll gladly help you work on it.  

Link to comment
Share on other sites

Awesome. I tried adapting that code to my script, and made a few modifications that shouldn't change anything (such as using vectors and the repeats and offsets I send to the function). But for some reason, it maps each matrix in an incorrect position. Here is an image of the result I get and the integrated function in my code (the red and green squares are unrelated). Let me know what I need to change please.

Snapshot_001.jpg

 

texture_set(string inventory, vector repeat, vector offset, float rot, integer start_x, integer start_y, integer end_x, integer end_y){    // scale_x and scale_y range from 0.1 to 100.0; none / default = 1.0    // offset_x and offset_y range from -1.0 to 1.0; none / default = 0.0    vector size;    size.x = end_x - start_x + 1;    size.y = end_y - start_y + 1;    vector apply_repeat;    apply_repeat.x = (1 / (float)size.x) * repeat.x;    apply_repeat.y = (1 / (float)size.y) * repeat.y;    integer i;    for(i = start_x; i <= end_x; i++) // loop through all cached chunks in range, X    {        integer j;        for(j = start_y; j <= end_y; j++) // loop through all cached chunks in range, Y        {            integer k = llListFindList(cache_coordinates, [<i,j,0>]);            integer linknum = llList2Integer(cache_linknum, k); // extract link number from the list index            // if texture is blank, clear the chunk            if(inventory == "")            {                llSetLinkPrimitiveParamsFast(linknum, [PRIM_TEXTURE, CHUNK_FACE, BLANK, <1, 1, 0>, <0, 0, 0>, 0]);                llSetLinkPrimitiveParamsFast(linknum, [PRIM_COLOR, CHUNK_FACE, <1, 1, 1>, 0]);                if(DEBUG > 1)                    llOwnerSay("Cleared texture on the chunk " + linknum_to_coordinates_str(linknum));            }            else            {                vector apply_offset;                apply_offset.x = (((float)i - (float)start_x) / (float)size.x) + offset.x;                apply_offset.y = (((float)j - (float)start_y) / (float)size.y) + offset.y;                llSetLinkPrimitiveParamsFast(linknum, [PRIM_TEXTURE, CHUNK_FACE, inventory, apply_repeat, apply_offset, rot]);                llSetLinkPrimitiveParamsFast(linknum, [PRIM_COLOR, CHUNK_FACE, <1, 1, 1>, 1]);                if(DEBUG > 1)                    llOwnerSay("Assigned texture " + inventory + " to the chunk " + linknum_to_coordinates_str(linknum));            }        }    }}

 

Link to comment
Share on other sites

  • 3 weeks later...
  • 2 weeks later...

Apologies for not getting back to you.  With the rez issues and one of my burlesque troupes taking what looks to be a 2-month holiday, not to mention the release of Diablo 3, I haven't been in SL very much to troubleshoot the script.  I did quickly load it up and note that my test wasn't working correctly.  But that just means I have to troubleshoot the individual functions to start with and be sure I fully understand how they work.  I'll try to get some work in on it by the end of June.  Diablo 3 has lost some of its lustre (*cough*at least until the next patch*cough*), so I'll try to give SL a little time.

Link to comment
Share on other sites

  • 3 weeks later...

Hello again!  Apologies for the delay.  I wanted to let you know that I haven't forgotten about the script.  I just spent the last 4 hours learning how to offset textures.  Such fun.  I *think* I understand how to code it correctly now, though I have absolutely no idea why it's set up the way it is.  Boggles the mind.  And the wiki documentation on [PRIM_TEXTURE] was incredibly unhelpful - ended up puzzling it out by trial & error.  Which was reeeeeeally annoying.

I'll get back to you when I've got a working prototype.  While I've sort of puzzled out how the repeat works, it's going to be extremely annoying to extend it to a random subset.  I don't have the equation yet and I don't see it, though I see the pattern.  Maybe.  I hope.  Ugh.

Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 4323 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...