// ===================================================================== // Flicker Flame // skimi3d script library // https://grid.skimi3d.com/scripts/flicker-flame // // A light that will not sit still. The candle in a window, the torch // on a wall, the campfire, the forge, the failing lamp in a corridor // nobody has fixed. // // A steady light reads as electric even when it is orange. The // unsteadiness is the whole effect, and it costs more than a steady // light does, so the settings are honest about how much you are // spending. // // Plain LSL. // ===================================================================== // --------------------------------------------------------------------- // Settings // --------------------------------------------------------------------- // The colour of the flame. // <1.0, 0.70, 0.30> candle or torch // <1.0, 0.45, 0.12> a hot fire // <0.75, 0.85, 1.0> a failing fluorescent tube vector COLOUR = <1.0, 0.70, 0.30>; // The brightness it hovers around, from 0.0 to 1.0. float BRIGHTNESS = 0.6; // How far it strays, as a share of the brightness. 0.25 is a candle in // a still room; 0.6 is a fire in the wind; 1.0 is a fault. float UNSTEADINESS = 0.30; // How far the light reaches, in metres. float RADIUS = 6.0; // Seconds between changes. This is the dial that costs something. // 0.25 is plenty for a candle. Below about 0.1 you are paying for // changes that never reach anybody, because the region only sends // updates around eleven times a second whatever a script does. float SPEED = 0.25; // Which face glows with the flame. -1 is every face, and a face number // lets the flame glow while the candlestick does not. integer FACE = -1; // How much that face glows at full brightness, from 0.0 to 1.0. float GLOW = 0.12; // Is it burning as soon as it is rezzed? integer START_LIT = TRUE; // Touch to blow it out and light it again. integer TOUCH_TO_TOGGLE = TRUE; // Who may touch it. 0 = everyone, 1 = only the owner, 2 = same group. integer WHO_MAY_TOUCH = 0; // --------------------------------------------------------------------- // Everything below here is the machinery // --------------------------------------------------------------------- integer gLit = FALSE; flicker() { // A share above and below the brightness, never below nothing and // never above full. Clamping matters: a flame that goes past 1.0 // spends part of its life at the ceiling and stops flickering // there, which reads as a stutter rather than a flame. float swing = BRIGHTNESS * UNSTEADINESS; float b = BRIGHTNESS + llFrand(swing * 2.0) - swing; if (b < 0.0) b = 0.0; if (b > 1.0) b = 1.0; // The reach wavers with the brightness rather than on its own, so // the pool of light on the floor breathes with the flame instead of // arguing with it. float r = RADIUS * (0.85 + (b / BRIGHTNESS) * 0.15); float g = GLOW * (b / BRIGHTNESS); if (g > 1.0) g = 1.0; // Both in one write. Two calls would be two updates for one moment // of the flame, and the region gathers them anyway. llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POINT_LIGHT, TRUE, COLOUR, b, r, 0.6, PRIM_GLOW, FACE, g]); } light(integer on) { gLit = on; if (on) { llSetTimerEvent(SPEED); flicker(); return; } // Out means out: the clock stops too. A script still ticking for a // light nobody can see is the most common way a region ends up // busy for no reason. llSetTimerEvent(0.0); llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POINT_LIGHT, FALSE, COLOUR, 0.0, 1.0, 1.0, PRIM_GLOW, FACE, 0.0]); } integer mayTouch(key who) { if (WHO_MAY_TOUCH == 1) return who == llGetOwner(); if (WHO_MAY_TOUCH == 2) return llSameGroup(who); return TRUE; } default { state_entry() { light(START_LIT); } timer() { flicker(); } touch_start(integer total) { if (!TOUCH_TO_TOGGLE) return; key who = llDetectedKey(0); if (!mayTouch(who)) { llRegionSayTo(who, 0, "This one is not yours to put out."); return; } light(!gLit); } on_rez(integer start) { light(START_LIT); } } // --------------------------------------------------------------------- // What a flickering light actually costs // // Every change is a message about this object to every viewer near // enough to care. At the default quarter second that is four a second, // for as long as it burns. One candle is nothing. Forty candles in a // tavern is a hundred and sixty messages a second going out of that // region, and it will be blamed on the mesh. // // Two ways to have your tavern and keep it cheap. Put the flicker on // one or two lights that carry the room and leave the rest as steady // glow, which nobody notices; or raise SPEED, because a candle looks // perfectly alive at half a second and half as expensive. // ---------------------------------------------------------------------