// MultiFaceImagemap.lsl - Bucky Barkley // // Sample Imagemap Implementation -- August 2008 -- version 1.0 // Bucky Barkley - JavaJoint@gmail.com // Permission granted to reuse, but give me credit -- note my name and // spell it correctly ;) // This is a multiple face imagemap implementation for the new touch functions. // One list controls how you define regions for the entire prim. // This implementation addresses rectangular regions. They can overlap. // Regions can be thought of as a stack floating over a prim face. Think // of how a windowing system works. // There are a few key concepts: // // 1) the entire imagemap resides in the list "myRegions". The entries // apply to specific faces (more about this in the FACE_STACK) // 2) each imagemap entry consists of two consecutive vectors // - the first vector contains x1, y1, and a FUNCTION_PTR reference // - the second vector contains x2, y2, and a FACE_STACK reference // 3) the FUNCTION_PTR reference should be some integer that you use to // look up which function to call // 4) the FACE_STACK reference is (the face * 100) + the 'height' of the // region above the face (think of 10, 20, 30, 40 ..) // For every entry in your imagemap, order from topmost regions to lower. // This allows you to check a click from the top down, and lets you overlap // your rectangles (lower ones wont get checked when higher ones are hit) // You may have more than one region with the exact same stacking order. // This allows you to bind multiple actions to areas where there is an overlap. // A sample imagemap: // [ // <.25,.25,PLAY_SOUND>, <.5,.5,120> // ] // Note that coordinates are given as a float from 0 - 1.0 // This one defines a bounding box from .25, .25 (lower left) to // .5, .5 (upper right) // If hit, our FUNCTION_PTR is "PLAY_SOUND" (think of an integer with some // value you can compare against, such as 100) // One more sample: // [ // <.35,.35,GIVE_TEXTURE>, <.5,.5,130>, // <.25,.25,PLAY_SOUND>, <.5,.5,120>, // <.25,.25,CHANGE_COLOR>, <.5,.5,220> // ] // // The sample above shows 2 entries for face #1 (130, and then 120), and // one entry for face #2 (220) // It also shows some rectangle overlap. In this case, GIVE_TEXTURE (130) is // higher in the stacking order than PLAY_SOUND. A hit will not pass through // to the rectangle below. To get more than one action from one hit, give them // the same FACE_STACK value (in this case, try 120 and 120) // IMPORTANT NOTE: group all entries for a face together in the myRegions list. // You should also put them in top-down order (FACE_STACK) within each face // that you plan to use. Example: 910, 900, 620, 610, 600, 110, 100 etc... // define constants for functions we will check for in doCmd(); integer PLAY_SOUND = 100; integer GIVE_TEXTURE = 200; integer ROT_X_INCR = 1000; integer ROT_X_DECR = 1100; integer ROT_Y_INCR = 1200; integer ROT_Y_DECR = 1300; integer ROT_Z_INCR = 1400; integer ROT_Z_DECR = 1500; integer SCALE_X_INCR = 2000; integer SCALE_X_DECR = 2100; integer SCALE_Y_INCR = 2200; integer SCALE_Y_DECR = 2300; integer SCALE_Z_INCR = 2400; integer SCALE_Z_DECR = 2500; // this is the Imagemap right here! // for each face, define in FACE_STACK order (top down) list myRegions = [ // face 2 - play with scale <0,0,SCALE_X_DECR>, <.33,1,270>, <.66,0,SCALE_X_INCR>, <1,1,270>, <0,0,SCALE_Y_DECR>, <1,.33,270>, <0,.66,SCALE_Y_INCR>, <1,1,270>, // face 1 - play with rotation <0,0,ROT_X_DECR>, <.33,1,170>, <.66,0,ROT_X_INCR>, <1,1,170>, <0,0,ROT_Y_DECR>, <1,.33,170>, <0,.66,ROT_Y_INCR>, <1,1,170>, <.25,.25,PLAY_SOUND>, <1,1,120> ]; // cursors - this is an optimization so that for any given incoming hit, we only // check the entries for its face // // a list of vectors, used as an index into imagemap entries per face // position in imagemap list, top of stack, how many entries list cursors = [ <0,0,0>, <0,0,0>, <0,0,0>, <0,0,0>, <0,0,0>, <0,0,0>, <0,0,0>, <0,0,0>, <0,0,0>, <0,0,0> ]; integer i; integer listLen; integer curCursorFace = -1; float curTopOfStack = 0.0; integer curNumEntries = 0; integer startOfFace = -1; integer gotHits = 0; integer curHitFace; vector thePoint; vector myRot; vector myScale; list hitVals = []; updateCursors(string whichStr, float curStack, integer i) { if ((integer)whichStr != curCursorFace) { curNumEntries = 0; startOfFace = i; curCursorFace = (integer)whichStr; curTopOfStack = curStack; } curNumEntries++; writeCursor(startOfFace, curTopOfStack, curNumEntries); } writeCursor(integer startFace, float curTopOfStack, integer curNumEntries) { vector curEntry = ; cursors = llListReplaceList(cursors, [curEntry], curCursorFace, curCursorFace); } resetMap() { for (i = 1; i < listLen; i += 2) { vector curVec = llList2Vector(myRegions, i); float curStack = curVec.z; string whichStr = llGetSubString((string)curVec.z, 0, 0); updateCursors(whichStr, curStack, i - 1); } } list checkHit(integer theFace, vector thePoint) { // which portion of the imagemap are we interested in? // it depends on the face vector theCursor = llList2Vector(cursors, theFace); integer startPos = (integer)theCursor.x; integer endPos = startPos + (2 * (integer)theCursor.z); integer i; vector vec1; vector vec2; integer regionAction = 0; integer curStackPos = 0; list returnVals = []; // this is a global so that we have a faster means of checking // our return list gotHits = 0; for (i = startPos; i < endPos; i += 2) { vec1 = llList2Vector(myRegions, i); vec2 = llList2Vector(myRegions, i + 1); // if we already have a hit, we dont need to // look lower in the stack // NOTE: we do keep checking for multiple regions that // have the same stack value.. this lets us join // rectangles if (gotHits && (vec2.z != curStackPos)) { jump stopChecking; } // a hit? if ((thePoint.x >= vec1.x) && (thePoint.x <= vec2.x) && (thePoint.y >= vec1.y) && (thePoint.y <= vec2.y)) { gotHits++; // llSay(0, "hit with stack " + (string)vec2.z); curStackPos = (integer)vec2.z; // for each hit, we add 5 elements to the returned list // the FUNCTION_PTR (vec1.z) // the stack value we hit (FACE_STACK) // position in myRegions (in case we need to look up the bounding box) // the x position of the hit // the y position of the hit returnVals += [vec1.z, curStackPos, i, thePoint.x, thePoint.y]; } } @stopChecking; return returnVals; } doCmd(integer whichCmd, integer whichHit, list allHitDetails) { // for demo integer needRot = 0; integer needScale = 0; // llSay(0, "cmd is " + (string) whichCmd); if (whichCmd == PLAY_SOUND) { llSay(0, "play sound"); } else if (whichCmd == GIVE_TEXTURE) { llSay(0, "give texture"); } else if (whichCmd == ROT_X_INCR) { myRot.x = addRot(myRot.x, 5); needRot++; } else if (whichCmd == ROT_X_DECR) { myRot.x = addRot(myRot.x, -5); needRot++; } else if (whichCmd == ROT_Y_INCR) { myRot.y = addRot(myRot.y, 5); needRot++; } else if (whichCmd == ROT_Y_DECR) { myRot.y = addRot(myRot.y, -5); needRot++; } else if (whichCmd == ROT_Z_INCR) { myRot.z = addRot(myRot.z, 5); needRot++; } else if (whichCmd == ROT_Z_DECR) { myRot.z = addRot(myRot.z, -5); needRot++; } else if (whichCmd == SCALE_X_INCR) { myScale.x = addScale(myScale.x, .1); needScale++; } else if (whichCmd == SCALE_X_DECR) { myScale.x = addScale(myScale.x, -.1); needScale++; } else if (whichCmd == SCALE_Y_INCR) { myScale.y = addScale(myScale.y, .1); needScale++; } else if (whichCmd == SCALE_Y_DECR) { myScale.y = addScale(myScale.y, -.1); needScale++; } else if (whichCmd == SCALE_Z_INCR) { myScale.z = addScale(myScale.z, .1); needScale++; } else if (whichCmd == SCALE_Z_DECR) { myScale.z = addScale(myScale.z, -.1); needScale++; } if (needRot) { doRot(); } if (needScale) { doScale(); } } integer addRot(float curVal, integer theVal) { integer tmpVal = (integer)curVal + theVal; if (tmpVal >= 360) { tmpVal = 0 + theVal; } if (tmpVal < 0) { tmpVal = 360 + theVal; } return tmpVal; } float addScale(float curVal, float theVal) { float tmpVal = curVal + theVal; if (tmpVal >= 10) { tmpVal = 10; } if (tmpVal < .1) { tmpVal = .1; } return tmpVal; } doRot() { vector eul = myRot; eul *= DEG_TO_RAD; rotation quat = llEuler2Rot(eul); llSetRot(quat); } doScale() { llSetScale(myScale); } default { state_entry() { listLen = llGetListLength(myRegions); resetMap(); rotation rot = llGetRot(); vector eulerAngle = llRot2Euler(rot); //convert to Euler angle. myRot = eulerAngle * RAD_TO_DEG; //convert euler angle to euler rotation myScale = llGetScale(); } touch_start(integer total_number) { // on the beta grid? curHitFace = llDetectedTouchFace(0); thePoint = llDetectedTouchST(0); // just testing on main grid? // curHitFace = 1; // thePoint = <.85,.85,0>; hitVals = checkHit(curHitFace, thePoint); if (gotHits) { integer j; for (j = 0; j < gotHits; j++) { // most of the time we just need to look at the function ptr // if the command needs more info, look at the current hit (j) and // the list of values to access (5 consecutive elements for each hit) // .. that would be ((j * 5) + 0) .. ((j * 5) + 4) doCmd(llList2Integer(hitVals, (j * 5)), j, hitVals); } } else { // llSay(0, "no hit..."); } } }