Turret rotation
A turret in a vehicle: the ring (Turret) turns only around the vehicle’s up axis and the barrel
(Barrel) only tilts, each at its own top speed, following the camera of whoever sits on the
root prim. It applies ConstrainYaw, ConstrainPitch and StepRotation from
Constraining rotations.
What to link
Section titled “What to link”- The base (the vehicle, or a stand) is the root prim. The script goes in it, and the avatar sits on it.
- A child prim named
Turret: the ring that turns. - A child prim named
Barrel: the part that tilts.
The script finds the ring and barrel by name with LinkByName; see
Working with link numbers for how that works and how
to look the links up again when the linkset changes.
The script
Section titled “The script”The script is complete and can be pasted as is. It includes the functions it uses (folded at the top); Constraining rotations is the reference for those functions and their limits.
37 collapsed lines
// Link number of a prim by name (see Find links by name).integer LinkByName(string needle){ integer prims = llGetNumberOfPrims() + 1; while (--prims) if (llGetLinkName(prims) == needle) return prims; return FALSE;}
rotation ConstrainYaw(rotation R, vector N){ vector U = N; vector L = llRot2Fwd(R) % U; vector F = llVecNorm(U % L); L = llVecNorm(U % F); return llAxes2Rot(F, L, U);}
rotation ConstrainPitch(rotation R, vector N){ vector F = llRot2Fwd(R); vector L = llVecNorm(F % N); vector U = -llVecNorm(F % L); L = U % F; return llAxes2Rot(F, L, U);}
// Turns from towards to by at most maxAngle radians, taking the shorter way round.rotation StepRotation(rotation from, rotation to, float maxAngle){ rotation delta = (ZERO_ROTATION / from) * to; // q and -q are the same rotation; pick the one with the smaller angle. if (delta.s < 0.0) delta = <-delta.x, -delta.y, -delta.z, -delta.s>; if (llRot2Angle(delta) <= maxAngle) return to; return from * llAxisAngle2Rot(llRot2Axis(delta), maxAngle);}
integer LinkTurret;integer LinkBarrel;vector hardpointNormal = <0.0, 0.0, 1.0>; // the turret's up axis, in the root's frame; unit lengthvector turretBase = <0.0, 0.0, 1.0>; // the barrel's pivot, in the root's framefloat turretTraverseSpeed = 1.0; // radians per secondfloat turretPitchSpeed = 0.5; // radians per secondfloat interval = 0.1; // seconds between updatesrotation lastYaw = ZERO_ROTATION;rotation lastPitch = ZERO_ROTATION;
default{ state_entry() { LinkTurret = LinkByName("Turret"); LinkBarrel = LinkByName("Barrel"); llSitTarget(<0.0, 0.0, 0.5>, ZERO_ROTATION); }
changed(integer change) { if (change & CHANGED_LINK) { key avatar = llAvatarOnSitTarget(); if (avatar) llRequestPermissions(avatar, PERMISSION_TRACK_CAMERA); else llSetTimerEvent(0.0); } }
run_time_permissions(integer permissions) { if (permissions & PERMISSION_TRACK_CAMERA) llSetTimerEvent(interval); }
timer() { // The camera's rotation relative to the vehicle. rotation localRot = llGetCameraRot() / llGetRootRotation(); rotation targetYaw = ConstrainYaw(localRot, hardpointNormal); // Remove the yaw; what remains is the barrel's tilt. rotation targetPitch = ConstrainPitch(localRot / targetYaw, hardpointNormal);
rotation yaw = StepRotation(lastYaw, targetYaw, turretTraverseSpeed * interval); rotation pitch = StepRotation(lastPitch, targetPitch, turretPitchSpeed * interval); lastYaw = yaw; lastPitch = pitch;
llSetLinkPrimitiveParamsFast(LINK_THIS, [ PRIM_LINK_TARGET, LinkTurret, PRIM_ROT_LOCAL, yaw, PRIM_LINK_TARGET, LinkBarrel, PRIM_ROT_LOCAL, pitch * yaw, PRIM_POS_LOCAL, turretBase + ((<0.6, 0.0, 0.0> * pitch) * yaw) ]); }}The barrel’s rotation is pitch * yaw: tilt first, then turn with the ring. Its position is
turretBase plus a 0.6 m offset rotated the same way.
Values to tune
Section titled “Values to tune”- turretTraverseSpeed and turretPitchSpeed: the ring’s and barrel’s top speeds in radians
per second.
StepRotationmoves each by at most its speed times interval per update. - interval: the seconds between updates.
- turretBase: the barrel’s pivot, in the root’s frame. The 0.6 m offset
<0.6, 0.0, 0.0>in thePRIM_POS_LOCALline is where the barrel sits relative to that pivot before it is rotated. - hardpointNormal: the turret’s up axis, in the root’s frame; it must be unit length.
- The sit target,
llSitTarget(<0.0, 0.0, 0.5>, ZERO_ROTATION)instate_entry: where the avatar sits on the root prim.
How to use it
Section titled “How to use it”Sit on the root prim. The script asks the seated avatar for
PERMISSION_TRACK_CAMERA and, once it is granted, starts
the timer. From then on the ring and barrel follow that avatar’s camera
(llGetCameraRot), relative to the vehicle: move the camera to aim.
Standing up stops the timer.
The script only aims. For firing, see the Combat feature.
- The turret and barrel must be child prims, and the script must be in the root prim with the sit target.
ConstrainYawandConstrainPitchhave no answer when the camera looks exactly along hardpointNormal (straight up or down); see the notes in Constraining rotations.
Source and licence
Section titled “Source and licence”From rotations.lsl in
NexiiLSL by Martin Pitt, © 2026, under the
MIT licence, at commit 996fbd7.
Changes from the original:
- The turret example called
stepRotation, which was not defined. It now usesStepRotation, written for Constraining rotations, and is a complete script with the sitting, permission and timer code it needs.frameRateis renamedinterval, because it is the time between updates. - The older, shorter turret example from the same file is left out; the updated one supersedes it.