Move Along Points
Give it a list of places and it travels between them: once, round and round, or there and back. Carries anybody sitting on it.
What it does
It turns your list of places into a journey the region works out properly, rather than a script nudging a prim along and hoping. Speed is set in metres a second and the timings follow from the distances, so changing one point does not force you to re-time the rest. It can pause at every stop, and it can turn to point the way it is going, which is the difference between a boat and a boat sailing sideways. Places are read as steps away from wherever you rez it, so the same build works anywhere you drop it.
What you can build with it
Ferries, lifts, patrolling airships, a cable car, a cloud crossing a valley, a fairground ride, a security drone doing rounds, a door too big to hinge. Anything that has to really move, as opposed to merely look like it: unlike Spin It, this one moves the object for real, so people can stand on it and it takes them with it.
How to use it
Settings you can change
10Edit these at the top of the script. Everything works on the defaults · change them only when you need to.
// =====================================================================
// Move Along Points
// skimi3d script library
// https://grid.skimi3d.com/scripts/move-along-points
//
// Give it a list of places and it travels between them: once, round
// and round, or there and back. Smoothly, at a speed you set in metres
// a second, and carrying anybody sitting on it.
//
// This is the one to reach for when Spin It is not enough, because
// this really moves the object. A ferry, a lift, a patrolling airship,
// a cloud crossing a valley, a door too big to hinge.
//
// Plain LSL. It uses keyframed motion, which the region works out
// properly rather than the viewers guessing at it.
// =====================================================================
// ---------------------------------------------------------------------
// Settings
// ---------------------------------------------------------------------
// The places it visits, in order. By default these are read as steps
// away from wherever you rez the object, along the region's own axes:
// X is east, Y is north, Z is up. That way the same build works
// anywhere you drop it.
//
// The example below walks a ten by eight rectangle on the flat.
list POINTS = [
< 0.0, 0.0, 0.0>,
< 0.0, 10.0, 0.0>,
< 8.0, 10.0, 0.0>,
< 8.0, 0.0, 0.0>
];
// Read POINTS as real region coordinates instead of as steps from where
// it stands. Useful when you have copied positions out of the build
// tool, but the object then only works in that one spot.
integer ABSOLUTE = FALSE;
// How fast it travels, in metres a second. Walking pace is about 1.5,
// a running avatar about 5, a slow boat about 3.
float SPEED = 1.5;
// 0 = go once and stop
// 1 = round and round for ever
// 2 = there and back, for ever
integer MODE = 1;
// Seconds to wait at every place before moving on. 0.0 means it never
// stops. Anything under a tenth of a second is treated as no wait.
float WAIT_AT_EACH = 0.0;
// Turn so that it points the way it is going. Leave this off for a
// lift or a cloud; turn it on for a boat, a cart or a bird.
integer TURN_TO_FACE = FALSE;
// Which way the front of your build points, in its own body. The
// default is its X. If your boat sails sideways, this is the line.
vector NOSE = <1.0, 0.0, 0.0>;
// Should it set off as soon as it is rezzed?
integer START_MOVING = TRUE;
// Touch to stop it, touch again to send it on.
integer TOUCH_TO_TOGGLE = TRUE;
// Who may touch it. 0 = everyone, 1 = only the owner, 2 = only people
// wearing the same group tag.
integer WHO_MAY_TOUCH = 0;
// ---------------------------------------------------------------------
// Everything below here is the machinery
// ---------------------------------------------------------------------
// Nothing shorter than this counts as a journey. Two points a
// millimetre apart would otherwise ask for a frame lasting no time at
// all, and the region has to divide by that.
float MIN_LEG = 0.01;
// The shortest a frame may last. Keyframed motion measures in whole
// milliseconds and a frame of nothing confuses it.
float MIN_TIME = 0.1;
integer gGoing = FALSE;
// Build the whole journey as a list of steps, because that is what
// keyframed motion wants: not "go to here" but "go this far from where
// you are".
list plan()
{
vector here = llGetPos();
rotation face = llGetRot();
// The settings first become real places, once, so that the rest of
// this only ever subtracts one place from another.
list places = [];
integer n = llGetListLength(POINTS);
integer i;
for (i = 0; i < n; ++i)
{
vector p = llList2Vector(POINTS, i);
if (!ABSOLUTE) p = here + p;
places += [p];
}
// ROUND AND ROUND HAS TO COME HOME, AND THIS IS WHY.
//
// A loop does not replay the same places. It replays the same
// STEPS, starting from wherever the last lap happened to end. If
// the steps do not add up to nothing, the object is somewhere new
// at the end of every lap, and it walks off across the region a
// little further each time. It is a slow, baffling drift, and by
// the time anybody notices, the thing is three regions away.
//
// So a loop gets a closing step home, added here rather than left
// for you to remember.
if (MODE == 1) places += [here];
list frames = [];
vector from = here;
integer count = llGetListLength(places);
for (i = 0; i < count; ++i)
{
vector to = llList2Vector(places, i);
vector step = to - from;
float len = llVecMag(step);
if (len >= MIN_LEG)
{
float t = len / SPEED;
if (t < MIN_TIME) t = MIN_TIME;
if (TURN_TO_FACE)
{
// Point the nose along this leg. The turn is spread
// over the leg, so it swings round as it travels
// rather than pivoting at the corner.
rotation want = llRotBetween(llVecNorm(NOSE), llVecNorm(step));
// Keyframes carry the turn from the last one, so what
// goes in the list is the difference, not the heading.
frames += [step, llRotInverse(face) * want, t];
face = want;
}
else
{
frames += [step, ZERO_ROTATION, t];
}
from = to;
}
if (WAIT_AT_EACH >= MIN_TIME)
{
// A wait is a step of no distance that still takes time.
frames += [ZERO_VECTOR, ZERO_ROTATION, WAIT_AT_EACH];
}
}
return frames;
}
go(integer on)
{
if (!on)
{
llSetKeyframedMotion([], [KFM_COMMAND, KFM_CMD_STOP]);
gGoing = FALSE;
return;
}
// Two things make keyframed motion do nothing at all, without a
// word of complaint. Both are worth saying out loud, because
// silence here costs an hour.
if (llGetStatus(STATUS_PHYSICS))
{
llOwnerSay("I cannot move: this object is physical, and keyframed motion only works on things that are not. Turn Physical off in the build tool.");
return;
}
if (llGetAttached())
{
llOwnerSay("I cannot move: keyframed motion does nothing while I am worn.");
return;
}
list frames = plan();
if (llGetListLength(frames) == 0)
{
llOwnerSay("I have nowhere to go: every place in POINTS is where I already am.");
return;
}
integer mode = KFM_FORWARD;
if (MODE == 1) mode = KFM_LOOP;
if (MODE == 2) mode = KFM_PING_PONG;
llSetKeyframedMotion(frames, [KFM_MODE, mode]);
gGoing = TRUE;
}
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()
{
// The journey is worked out from where the object is standing
// now, so it has to be planned after every move, not once and
// for ever.
go(START_MOVING);
}
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 stop.");
return;
}
go(!gGoing);
}
on_rez(integer start)
{
// A rezzed copy stands somewhere new, so the plan it was
// carrying points at the old place. Draw it again.
llResetScript();
}
changed(integer what)
{
// After a region restart the object is back at its start but
// the motion is not. Plan again from where it really is.
if (what & CHANGED_REGION_START) llResetScript();
}
}
Copy it, make a new script inworld, paste over what is there and save. Nothing here phones home.