// 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 } } } }