skimi3d Grid Log in Visit the grid
All scripts
Doors and ways in

Swing Door

A door that swings open on its hinge edge when touched, and closes itself again. Works on a lone prim or as part of a linkset.

Goes inThe door leaf, on its own or linked
Setup9 to set
Code229 lines
Open toOpen to everyone

What it does

Touching the door swings it open around one of its edges and starts a clock. When the clock runs out it swings shut again. Every position along the way is worked out from the shut one and never from the last step, so a door that has been opened a thousand times still sits exactly where it started. It sets local position and rotation rather than region ones, and that is what lets the same script work inside a linkset as well as on a prim standing by itself.

What you can build with it

House doors, garden gates, shop shutters, a hatch in a floor, a drawbridge. Anything hinged. Set WHO_MAY_OPEN to 1 and it becomes a door only you can open; set it to 2 and it opens for anyone wearing your group tag, which is the usual arrangement for a shared build or a back room.

How to use it

1 Build the door leaf as its own prim and stand it in the doorway, shut.
2 If you want it attached to a house, select the door first and the house last, then link. The last thing you select becomes the root, and the door needs to be a child.
3 Drop this script into the door leaf.
4 Touch it. If it turns around its middle like a revolving door, the HINGE line is wrong; if it swings the wrong way, put a minus in front of ANGLE.
5 Optional: drop a door sound into the same prim's Contents and write its name into SOUND_OPEN and SOUND_SHUT.

Settings you can change

9

Edit these at the top of the script. Everything works on the defaults · change them only when you need to.

HINGE Which edge the door hangs on, measured in the door's own body, where 0.5 is a face and 0.0 is the middle. The default hangs it down the -Y edge. Get this wrong and the door turns like a revolving door. <0.0, -0.5, 0.0>
ANGLE How far it swings, in degrees. A negative number swings it the other way, which is quicker than moving the hinge to the opposite edge. 90.0
AXIS The axis it turns around, in the door's own body. Upright is what a normal door wants; change it for a hatch or a drawbridge. <0.0, 0.0, 1.0>
SWING_TIME How long the swing takes, in seconds. 0.0 snaps it open with no animation. If the swing looks choppy, raise this rather than looking for a step count. 0.7
AUTO_CLOSE Seconds it stays open before closing itself. 0.0 means it stays open until someone touches it again. 8.0
WHO_MAY_OPEN 0 for everyone, 1 for the owner alone, 2 for anyone wearing the same group tag as the door. 0
SOUND_OPEN Name of a sound in the door's Contents, played as it opens. Empty means a silent door. (empty)
SOUND_SHUT Name of a sound in the door's Contents, played as it shuts. (empty)
VOLUME How loud those sounds are, from 0.0 to 1.0. 0.5
The code LSL · 229 lines · updated 8 Sep 2026 Download .lsl
// =====================================================================
//  Swing Door
//  skimi3d script library · https://grid.skimi3d.com/scripts/swing-door
//
//  A door that swings open around its hinge edge when touched, and
//  closes itself again afterwards. It works on a single prim and on a
//  child prim inside a linkset, which is the usual case: the door leaf
//  linked to the house.
//
//  Plain LSL. No OSSL, nothing that phones home, nothing that only
//  works on this grid.
//
//  Everything worth changing is in the block below. The rest is
//  commented so you can read why it does what it does.
// =====================================================================


// ---------------------------------------------------------------------
//  Settings
// ---------------------------------------------------------------------

// Which edge the door hangs on, measured in the door's own body, where
// 0.5 is a face and 0.0 is the middle. <0.0, -0.5, 0.0> means the hinge
// runs down the -Y edge. If your door swings around its middle like a
// revolving door, this is the line you got wrong.
vector HINGE = <0.0, -0.5, 0.0>;

// How far it swings, in degrees. A negative number swings it the other
// way, which is usually quicker than moving the hinge to the far edge.
float ANGLE = 90.0;

// The axis it turns around, in the door's own body. <0.0, 0.0, 1.0> is
// upright and is what a normal door wants. Change it for a hatch or a
// drawbridge.
vector AXIS = <0.0, 0.0, 1.0>;

// How long the swing takes, in seconds. 0.0 snaps it open with no
// animation at all.
float SWING_TIME = 0.7;

// Seconds it stays open before it closes itself. 0.0 means it stays
// open until someone touches it again.
float AUTO_CLOSE = 8.0;

// Who may open it.
//   0 = everyone
//   1 = only the owner
//   2 = only people wearing the same group tag as the door
integer WHO_MAY_OPEN = 0;

// Sounds. Drop a sound into the door's Contents and write its name
// here. Leave the name empty for a silent door.
string SOUND_OPEN = "";
string SOUND_SHUT = "";
float  VOLUME     = 0.5;


// ---------------------------------------------------------------------
//  Everything below here is the machinery
// ---------------------------------------------------------------------

// One step every tenth of a second, and not one step more.
//
// OpenSim gathers object updates and sends them out in bundles, about
// eleven times a second. Writing the door's position more often than
// that does not make the swing smoother, because the extra positions
// never reach anyone's viewer. It only costs the region work. If your
// swing looks choppy, give it more TIME, not more steps.
float STEP = 0.1;

integer gOpen    = FALSE;   // is it standing open right now?
integer gMoving  = FALSE;   // mid-swing, ignore further touches
vector   gShutPos;          // where it sits when closed
rotation gShutRot;          // how it sits when closed
vector   gHinge;            // the hinge point, in the door's own body


// Put the door at some fraction of its swing. 0.0 is shut, 1.0 is
// fully open.
place(float f)
{
    rotation turn = llAxisAngle2Rot(AXIS, ANGLE * DEG_TO_RAD * f);
    rotation r    = turn * gShutRot;

    // The hinge is a fixed point of the door: it has to end up in the
    // same place before and after the turn. Everything else follows
    // from that one sentence.
    //
    //   shutPos + hinge * shutRot   ==   newPos + hinge * newRot
    //
    // Solved for the centre of the prim, which is what we can actually
    // set. Doing it this way means the door cannot drift, however many
    // times it swings, because every position is computed from the
    // closed one and never from the last step.
    vector p = gShutPos + gHinge * gShutRot - gHinge * r;

    llSetLinkPrimitiveParamsFast(LINK_THIS, [PRIM_POS_LOCAL, p, PRIM_ROT_LOCAL, r]);
}


swing(integer open)
{
    gMoving = TRUE;

    integer steps = (integer)(SWING_TIME / STEP);
    if (steps > 1)
    {
        integer i;
        for (i = 1; i < steps; ++i)
        {
            float f = (float)i / (float)steps;
            if (!open) f = 1.0 - f;
            place(f);
            llSleep(STEP);
        }
    }

    // Always land exactly on the end state. Without this last line a
    // door left on the second-to-last step would sit a degree ajar, and
    // that gap grows every time you change SWING_TIME.
    float end = 0.0;
    if (open) end = 1.0;
    place(end);

    gOpen   = open;
    gMoving = FALSE;
}


play(string name)
{
    if (name == "") return;

    if (llGetInventoryType(name) != INVENTORY_SOUND)
    {
        // Said to the owner only. A visitor cannot fix this and does
        // not need to hear about it.
        llOwnerSay("There is no sound called \"" + name + "\" in my Contents.");
        return;
    }

    llPlaySound(name, VOLUME);
}


integer mayOpen(key who)
{
    if (WHO_MAY_OPEN == 1) return who == llGetOwner();
    if (WHO_MAY_OPEN == 2) return llSameGroup(who);
    return TRUE;
}


default
{
    state_entry()
    {
        // The door learns its shut position from wherever it is
        // standing at this moment. Put it where it belongs BEFORE you
        // drop the script in.
        list p = llGetLinkPrimitiveParams(LINK_THIS, [PRIM_POS_LOCAL, PRIM_ROT_LOCAL]);
        gShutPos = llList2Vector(p, 0);
        gShutRot = llList2Rot(p, 1);

        // The hinge setting is given in fractions of the door, so that
        // the same numbers keep working when you resize it. Turned into
        // real metres here, once.
        vector s = llGetScale();
        gHinge = <HINGE.x * s.x, HINGE.y * s.y, HINGE.z * s.z>;

        gOpen   = FALSE;
        gMoving = FALSE;
        llSetTimerEvent(0.0);
    }

    touch_start(integer total)
    {
        if (gMoving) return;

        key who = llDetectedKey(0);
        if (!mayOpen(who))
        {
            llRegionSayTo(who, 0, "This door does not open for you.");
            return;
        }

        llSetTimerEvent(0.0);

        if (gOpen)
        {
            play(SOUND_SHUT);
            swing(FALSE);
            return;
        }

        play(SOUND_OPEN);
        swing(TRUE);

        if (AUTO_CLOSE > 0.0) llSetTimerEvent(AUTO_CLOSE);
    }

    timer()
    {
        llSetTimerEvent(0.0);
        if (!gOpen) return;

        play(SOUND_SHUT);
        swing(FALSE);
    }

    on_rez(integer start)
    {
        // A rezzed copy is standing somewhere new, so its idea of shut
        // is worthless. Learn it again.
        llResetScript();
    }

    changed(integer what)
    {
        // Linking or unlinking changes what a local position even means,
        // so the door has to measure itself again. Only when it is shut
        // and still, though: reset it mid-swing and it would take its
        // half-open position for the shut one and never close properly.
        if (what & CHANGED_LINK)
        {
            if (!gOpen && !gMoving) llResetScript();
        }
    }
}

Copy it, make a new script inworld, paste over what is there and save. Nothing here phones home.