~Fiona's Script Library~

← Back to Library

flip-book.lsl | 4.46 KB
Download
// Copyright 2025 by Fiona Sweet
// Free to use for any purpose
// Flip-Book Controller (Root prim: Book_Cover)
// Click cover to open. Click right page to advance, left page to go back.
// Closes to front cover after last spread or when going back from first spread.

string TEX_COVER  = "FrontCover";
list   ORDER = [
    "InsideFrontCover","Page_01",
    "Page_02","Page_03",
    "Page_04","Page_05",
    "Page_06","Page_07",
    "Page_08","Page_09",
    "Page_10","Page_11",
    "Page_12","Page_13",
    "Page_14","InsideBackCover"
]; // left,right pairs

string NAME_COVER = "Notes From Saturn";
string NAME_LEFT  = "Book_Left";
string NAME_RIGHT = "Book_Right";

integer LINK_COVER = -1;
integer LINK_LEFT  = -1;
integer LINK_RIGHT = -1;

integer gOpen = FALSE;
integer gSpread = 0;
integer gLastSpread = -1;

vector coverHome;          // local pos of cover when closed
vector coverAwayOffset = <0.0, 0.0, -1.0>; // move cover 1m behind when open so clicks hit pages

integer findLink(string pname) {
    integer i; integer n = llGetNumberOfPrims();
    for (i = 1; i <= n; ++i) if (llGetLinkName(i) == pname) return i;
    return -1;
}

integer texExists(string name) { return (llGetInventoryType(name) == INVENTORY_TEXTURE); }

showCover() {
    // textures + visibility
    llSetLinkPrimitiveParamsFast(LINK_COVER, [
        PRIM_TEXTURE, ALL_SIDES, TEX_COVER, <1,1,0>, <0,0,0>, 0.0,
        PRIM_COLOR,   ALL_SIDES, <1,1,1>, 1.0,
        PRIM_POS_LOCAL, coverHome
    ]);
    llSetLinkPrimitiveParamsFast(LINK_LEFT,  [PRIM_COLOR, ALL_SIDES, <1,1,1>, 0.0]);
    llSetLinkPrimitiveParamsFast(LINK_RIGHT, [PRIM_COLOR, ALL_SIDES, <1,1,1>, 0.0]);

    gOpen = FALSE;
    gSpread = 0;
}

applySpread(integer spread) {
    integer idx = spread * 2;
    string leftTex  = llList2String(ORDER, idx);
    string rightTex = llList2String(ORDER, idx + 1);

    // ensure pages visible
    llSetLinkPrimitiveParamsFast(LINK_LEFT,  [
        PRIM_TEXTURE, ALL_SIDES, leftTex,  <1,1,0>, <0,0,0>, 0.0,
        PRIM_COLOR,   ALL_SIDES, <1,1,1>, 1.0
    ]);
    llSetLinkPrimitiveParamsFast(LINK_RIGHT, [
        PRIM_TEXTURE, ALL_SIDES, rightTex, <1,1,0>, <0,0,0>, 0.0,
        PRIM_COLOR,   ALL_SIDES, <1,1,1>, 1.0
    ]);
}

openTo(integer spread) {
    // move cover out of the way so page prims receive touches
    llSetLinkPrimitiveParamsFast(LINK_COVER, [
        PRIM_COLOR,     ALL_SIDES, <1,1,1>, 0.0,
        PRIM_POS_LOCAL, coverHome + coverAwayOffset
    ]);

    gOpen = TRUE;
    gSpread = spread;
    applySpread(gSpread);
}

default
{
    state_entry()
    {
        LINK_COVER = findLink(NAME_COVER);
        LINK_LEFT  = findLink(NAME_LEFT);
        LINK_RIGHT = findLink(NAME_RIGHT);

        if (LINK_COVER < 0 || LINK_LEFT < 0 || LINK_RIGHT < 0) {
            llOwnerSay("Name prims exactly: 'Book_Cover' (root), 'Book_Left', 'Book_Right'. Link them with cover as root.");
            return;
        }

        // remember cover home position (local)
        list p = llGetLinkPrimitiveParams(LINK_COVER, [PRIM_POS_LOCAL]);
        coverHome = llList2Vector(p, 0);

        // quick check textures (warn only)
        integer missing = FALSE;
        if (!texExists(TEX_COVER)) { llOwnerSay("Missing texture: " + TEX_COVER); missing = TRUE; }
        integer i; integer L = llGetListLength(ORDER);
        for (i = 0; i < L; ++i) if (!texExists(llList2String(ORDER,i))) { missing = TRUE; llOwnerSay("Missing texture: " + llList2String(ORDER,i)); }
        if (missing) llOwnerSay("Some textures missing; check inventory names.");

        gLastSpread = (llGetListLength(ORDER) / 2) - 1; // 16 items -> 8 spreads -> last=7

        // Start closed
        showCover();
        llOwnerSay("📖 Ready. Click the cover to open. Right page = next, Left page = back.");
    }

    // Click on the cover prim opens the book
    touch_start(integer n)
    {
        if (!gOpen) openTo(0);
    }

    // Page prims send NEXT/PREV here
    link_message(integer sender, integer num, string msg, key id)
    {
        if (msg == "NEXT") {
            if (!gOpen) { openTo(0); return; }
            if (gSpread < gLastSpread) {
                gSpread++; applySpread(gSpread);
            } else {
                showCover(); // past the last spread -> close
            }
        }
        else if (msg == "PREV") {
            if (!gOpen) { openTo(0); return; }
            if (gSpread > 0) {
                gSpread--; applySpread(gSpread);
            } else {
                showCover(); // back from first spread -> close
            }
        }
    }
}