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.
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
Settings you can change
9Edit these at the top of the script. Everything works on the defaults · change them only when you need to.
// =====================================================================
// 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.