Movement, Rotation & Physics - Master the art of bringing objects to life with dynamic motion and realistic physics in virtual worlds.
Sorin Todys - Advanced LSL Expert
With over 20 years of virtual world development experience, Sorin specializes in physics-based scripting and dynamic object manipulation. All classes take place at our dedicated Alife Virtual School region.
Take your scripting skills to the next level by learning how to make objects move, rotate, and interact with the physics engine!
Upon completion, you will be able to:
In virtual worlds, everything exists in 3D space. To move and rotate objects, you need to understand vectors and rotations.
A vector is a set of three numbers representing a position or direction in 3D space:
vector position = ;
// Examples:
vector origin = <0.0, 0.0, 0.0>; // World center
vector above = <0.0, 0.0, 1.0>; // 1 meter up
vector forward = <1.0, 0.0, 0.0>; // 1 meter forward (X axis)
vector sideways = <0.0, 1.0, 0.0>; // 1 meter sideways (Y axis)
A rotation is represented by a quaternion - a complex mathematical structure with 4 components (x, y, z, s). Most of the time, you'll use built-in functions instead of calculating these manually.
rotation rot = ;
// Instead of calculating quaternions, use helper functions:
rotation rot_90_z = llEuler2Rot(<0, 0, PI/2>); // 90° around Z axis
rotation rot_180_x = llEuler2Rot(); // 180° around X axis
rotation rot_45_y = llEuler2Rot(<0, PI/4, 0>); // 45° around Y axis
| Function | Purpose | Example |
|---|---|---|
llGetPos() |
Get current position | vector pos = llGetPos(); |
llSetPos() |
Set position (global coords) | llSetPos(<128, 128, 25>); |
llGetRot() |
Get current rotation | rotation rot = llGetRot(); |
llSetRot() |
Set rotation | llSetRot(llEuler2Rot(<0,0,PI/2>)); |
llEuler2Rot() |
Convert angles to rotation | llEuler2Rot(<0, 0, PI/2>); |
llRot2Euler() |
Convert rotation to angles | vector angles = llRot2Euler(rot); |
// Move object up by 1 meter when touched
default
{
touch_start(integer num)
{
// Get current position
vector currentPos = llGetPos();
// Move up by adding to Z coordinate
vector newPos = currentPos + <0, 0, 1.0>;
// Set the new position
llSetPos(newPos);
llSay(0, "Moved up 1 meter!");
}
}
llSetPos() can only move objects 10 meters per callTo create smooth movement, we use timers to move objects in small increments.
// Smooth elevator that moves up and down
vector startPos;
vector endPos;
float moveSpeed = 0.5; // Meters per second
integer moving = FALSE;
integer goingUp = TRUE;
default
{
state_entry()
{
startPos = llGetPos();
endPos = startPos + <0, 0, 10.0>; // 10 meters up
llSetText("Elevator\nTouch to Call", <1,1,1>, 1.0);
}
touch_start(integer num)
{
if (!moving)
{
moving = TRUE;
llSetTimerEvent(0.1); // Update every 0.1 seconds
llSetText("Moving...", <1,1,0>, 1.0);
}
}
timer()
{
vector currentPos = llGetPos();
vector targetPos;
if (goingUp)
targetPos = endPos;
else
targetPos = startPos;
// Calculate distance to move this frame
vector direction = targetPos - currentPos;
float distance = llVecMag(direction);
if (distance < 0.1)
{
// Reached destination
llSetPos(targetPos);
llSetTimerEvent(0.0);
moving = FALSE;
goingUp = !goingUp; // Toggle direction
llSetText("Elevator\nTouch to Call", <1,1,1>, 1.0);
}
else
{
// Move towards target
vector moveVector = llVecNorm(direction) * (moveSpeed * 0.1);
llSetPos(currentPos + moveVector);
}
}
}
llVecMag() - Returns the length/magnitude of a vectorllVecNorm() - Normalizes a vector to length 1.0moveSpeed variable// Move entire linkset (all connected prims together)
default
{
touch_start(integer num)
{
// Get root prim position
vector rootPos = llGetRootPosition();
// Move entire linkset up 5 meters
llSetLinkPrimitiveParamsFast(LINK_ROOT, [
PRIM_POSITION, rootPos + <0, 0, 5.0>
]);
llSay(0, "Entire object moved!");
}
}
// Rotate object 90 degrees when touched
default
{
touch_start(integer num)
{
// Get current rotation
rotation currentRot = llGetRot();
// Create a 90-degree rotation around Z axis
rotation rotateBy = llEuler2Rot(<0, 0, PI/2>);
// Apply the rotation
llSetRot(currentRot * rotateBy);
llSay(0, "Rotated 90 degrees!");
}
}
For objects that should spin continuously (like fans, wheels, propellers), use llTargetOmega():
// Spinning fan or propeller
float rotationSpeed = 2.0; // Rotations per second
default
{
state_entry()
{
// Spin around Z axis at 2 rotations per second
llTargetOmega(<0, 0, 1>, rotationSpeed * TWO_PI, 1.0);
llSetText("Spinning Fan", <0,1,1>, 1.0);
}
touch_start(integer num)
{
// Toggle on/off
if (rotationSpeed > 0)
{
llTargetOmega(ZERO_VECTOR, 0, 0);
rotationSpeed = 0.0;
llSetText("Fan OFF", <1,0,0>, 1.0);
}
else
{
rotationSpeed = 2.0;
llTargetOmega(<0, 0, 1>, rotationSpeed * TWO_PI, 1.0);
llSetText("Fan ON", <0,1,0>, 1.0);
}
}
}
// Smoothly rotate object to face target direction
rotation targetRotation;
float rotationSpeed = 0.5; // Radians per second
integer rotating = FALSE;
default
{
touch_start(integer num)
{
// Rotate to face toucher
key toucher = llDetectedKey(0);
vector toucherPos = llList2Vector(llGetObjectDetails(toucher, [OBJECT_POS]), 0);
vector myPos = llGetPos();
// Calculate direction to toucher
vector direction = toucherPos - myPos;
// Convert to rotation (facing toucher)
targetRotation = llRotBetween(<1,0,0>, llVecNorm(direction));
rotating = TRUE;
llSetTimerEvent(0.1);
}
timer()
{
rotation currentRot = llGetRot();
rotation deltaRot = targetRotation / currentRot;
vector axis;
float angle;
llRot2Axis(deltaRot, axis, angle);
if (llFabs(angle) < 0.05)
{
llSetRot(targetRotation);
llSetTimerEvent(0.0);
rotating = FALSE;
llSay(0, "Facing you!");
}
else
{
float stepAngle = rotationSpeed * 0.1;
if (stepAngle > llFabs(angle))
stepAngle = llFabs(angle);
rotation stepRot = llAxisAngle2Rot(axis, stepAngle);
llSetRot(currentRot * stepRot);
}
}
}
When you enable physics on an object, it becomes affected by gravity, collisions, and forces.
// Enable/disable physics
llSetStatus(STATUS_PHYSICS, TRUE); // Enable physics
llSetStatus(STATUS_PHYSICS, FALSE); // Disable physics
// Other useful flags:
llSetStatus(STATUS_PHANTOM, TRUE); // No collisions
llSetStatus(STATUS_ROTATE_X, FALSE); // Lock X-axis rotation
llSetStatus(STATUS_ROTATE_Y, FALSE); // Lock Y-axis rotation
llSetStatus(STATUS_ROTATE_Z, FALSE); // Lock Z-axis rotation
// Physics-based cannon ball
default
{
state_entry()
{
// Make object physical
llSetStatus(STATUS_PHYSICS, TRUE);
// Launch upward and forward
vector force = <10.0, 0.0, 15.0>; // Forward and up
llApplyImpulse(force, FALSE);
llSetText("Cannonball!", <1,0.5,0>, 1.0);
}
collision_start(integer num)
{
llSay(0, "BOOM! Hit something!");
llDie(); // Remove object
}
}
// Simple hover vehicle
float hoverHeight = 1.0;
float thrust = 5.0;
default
{
state_entry()
{
llSetStatus(STATUS_PHYSICS, TRUE);
llSetBuoyancy(1.0); // Neutralize gravity
llSetTimerEvent(0.1);
}
touch_start(integer num)
{
// Apply forward thrust
vector forceDirection = <1, 0, 0> * llGetRot();
llApplyImpulse(forceDirection * thrust, FALSE);
}
timer()
{
// Maintain hover height
vector pos = llGetPos();
float groundHeight = llGround(ZERO_VECTOR);
float currentHeight = pos.z - groundHeight;
if (currentHeight < hoverHeight)
{
llApplyImpulse(<0, 0, 0.5>, FALSE);
}
}
}
Let's build a complete multi-floor elevator system with call buttons.
// Main Elevator Platform Script
list floors; // List of floor heights
integer currentFloor = 0;
integer targetFloor = 0;
integer moving = FALSE;
float moveSpeed = 2.0;
integer comChannel = -5678;
default
{
state_entry()
{
// Define floor positions (Z heights)
floors = [0.0, 10.0, 20.0, 30.0]; // 4 floors
// Listen for floor requests
llListen(comChannel, "", NULL_KEY, "");
llSetText("Elevator\nFloor: " + (string)(currentFloor + 1), <1,1,1>, 1.0);
}
listen(integer channel, string name, key id, string msg)
{
// Commands: "goto_0", "goto_1", "goto_2", "goto_3"
if (llSubStringIndex(msg, "goto_") == 0)
{
targetFloor = (integer)llGetSubString(msg, 5, -1);
if (targetFloor != currentFloor && !moving)
{
moving = TRUE;
llSetTimerEvent(0.1);
llSetText("Moving to Floor " + (string)(targetFloor + 1), <1,1,0>, 1.0);
}
}
}
timer()
{
vector currentPos = llGetPos();
float targetHeight = llList2Float(floors, targetFloor);
vector basePos = llGetPos();
basePos.z = targetHeight;
float distance = llFabs(currentPos.z - targetHeight);
if (distance < 0.05)
{
// Arrived at floor
currentPos.z = targetHeight;
llSetPos(currentPos);
currentFloor = targetFloor;
moving = FALSE;
llSetTimerEvent(0.0);
llSetText("Elevator\nFloor: " + (string)(currentFloor + 1), <0,1,0>, 1.0);
// Ding sound
llPlaySound("elevator_ding", 1.0);
}
else
{
// Move towards target
float moveAmount = moveSpeed * 0.1;
if (moveAmount > distance)
moveAmount = distance;
if (currentPos.z < targetHeight)
currentPos.z += moveAmount;
else
currentPos.z -= moveAmount;
llSetPos(currentPos);
}
}
touch_start(integer num)
{
// Manual floor selection
targetFloor = (currentFloor + 1) % llGetListLength(floors);
if (!moving)
{
moving = TRUE;
llSetTimerEvent(0.1);
}
}
}
// Elevator Call Button Script
integer myFloor = 0; // Change for each floor: 0, 1, 2, 3
integer comChannel = -5678;
default
{
state_entry()
{
llSetText("Call Elevator\nFloor " + (string)(myFloor + 1), <1,1,1>, 1.0);
}
touch_start(integer num)
{
llSay(comChannel, "goto_" + (string)myFloor);
llSetText("Called!\nFloor " + (string)(myFloor + 1), <0,1,0>, 1.0);
llSetTimerEvent(3.0);
}
timer()
{
llSetText("Call Elevator\nFloor " + (string)(myFloor + 1), <1,1,1>, 1.0);
llSetTimerEvent(0.0);
}
}
myFloor variable in each button (0, 1, 2, 3)floors list to match your building// DON'T: Too frequent
llSetTimerEvent(0.01); // 100 updates/second - wasteful
// DO: Optimal frequency
llSetTimerEvent(0.1); // 10 updates/second - smooth enough
llSetTimerEvent(0.2); // 5 updates/second - good for slow movement
// DON'T: Timer always running
timer()
{
if (moving)
{
// Do movement
}
// Timer keeps running even when not moving!
}
// DO: Stop timer when done
timer()
{
// Do movement
if (reachedDestination)
{
llSetTimerEvent(0.0); // Stop the timer
moving = FALSE;
}
}
// DON'T: Slow method
llSetPos(newPos);
llSetRot(newRot);
// DO: Fast method (for child prims)
llSetLinkPrimitiveParamsFast(LINK_THIS, [
PRIM_POSITION, newPos,
PRIM_ROTATION, newRot
]);
Task: Create a door that slides open sideways when touched and closes after 5 seconds.
Requirements:
Task: Create a collectible coin that spins continuously and disappears when touched.
Requirements:
Task: Create a platform that moves back and forth between two points.
Requirements:
Task: Create a teleport pad that moves an avatar to a destination with a visual effect.
Requirements:
Use: llSetLinkPrimitiveParamsFast() for effects and position changes
Task: Create a security camera that rotates back and forth scanning an area.
Requirements:
Task: Create a rideable vehicle with forward/backward movement and steering.
Requirements:
| Function | Purpose | Example |
|---|---|---|
llSetPos() |
Set position (10m limit) | llSetPos(pos + <0,0,1>); |
llSetRegionPos() |
Set position (no limit) | llSetRegionPos(<128,128,50>); |
llSetLocalPos() |
Child prim position | llSetLocalPos(<1,0,0>); |
llSetRot() |
Set rotation | llSetRot(llEuler2Rot(<0,0,PI/2>)); |
llTargetOmega() |
Continuous spinning | llTargetOmega(<0,0,1>, TWO_PI, 1.0); |
llApplyImpulse() |
Physics push | llApplyImpulse(<0,0,10>, FALSE); |
| Function | Purpose |
|---|---|
llVecMag(v) |
Get vector length/distance |
llVecNorm(v) |
Normalize to length 1.0 |
llVecDist(a, b) |
Distance between two points |
PI = 3.14159... (180 degrees)TWO_PI = 6.28318... (360 degrees)PI_BY_TWO = 1.57079... (90 degrees)ZERO_VECTOR = <0, 0, 0>ZERO_ROTATION = <0, 0, 0, 1>Congratulations on mastering LSL object manipulation!
You now have the skills to create dynamic, moving, and rotating objects that bring your virtual world to life.