February 13, 2014

Rotation: a one prim door

The simplest method to make a swinging door is to use a box prim that has half of its volume cut away.  The remaining visible section has its local Z axis on an edge.  You can swing the door with

       vector doorRot = llEuler2Rot( < 0, delta, 0 > );
       llSetRot( doorRot * llGetRot() );

where 'delta' is an angle (radians) that alternates positive and negative on each touch.

However, if you wish to make a Hobbit door from, say, a cylinder, you can still get the edge hinge effect without cutting the prim.   The trick is to move the center of the door as you rotate it so as to keep the global (region) position of the edge in the same place.

For this discussion, imagine a cylinder with Z sized down to make a disk.  Orient this with its local Y+ axis upward.  The hinge point will be where the prim local X+ axis intercepts the edge of the cylinder.  The prim will rotate around a vertical axis.

Place this script in the prim.  Then touch the prim.

integer isOpen = 0;
float swingAngleDeg = -80;

vector gPdoorOpen;                  // values in global (region) coordinates
vector gPdoorShut;                  // (note 1)
rotation gRdoorOpen; 
rotation gRdoorShut;

vector lPhingePoint;                // value in local (cylinder) coordinates

shiftTo( vector p, rotation r ) {

   // There will be a visual problem with this method (note 2) 

default {
state_entry() {
   // The closed position is determined by reset
   gPdoorShut = llGetPos();
   gRdoorShut = llGetRot();

   // The open rotation is an additional local Y axis rotation (note 3)

   vector eulerRot = < 0, DEG_TO_RAD*swingAngleDeg, 0 >;
   gRdoorOpen = llEuler2Rot(eulerRot) * gRdoorShut;

   // Calculate the open position (note 4)
   vector size = llGetScale();
   lPhingePoint = < size.x/2, 0, 0 >;
   gPdoorOpen = gPdoorShut + (lPhingePoint * gRdoorShut) 

                     - (lPhingePoint *     gRdoorOpen);

touch_start(integer nTouch) {
   isOpen = !isOpen;
   if ( isOpen ) {

   } else {


This script will work for any prim size or orientation.  Just remember to reset the script if you change the prim position, rotation, or size.
Note 1:
In this notation, the first letter is l or g and indicates local or global coordinates.  The second letter is P or R and indicates position or rotation.  The remaining letters describe which specific point.  

Note 2:
While rotating the center of the prim is moving in a straight line.  The result is that the hinge will wobble.  The correct movement is rotating the prim while moving the center of the prim in an arc about the hinge point.

There are two methods to do this:
  • shiftTo() can be a loop that performs a series of small calculated rotations, or 
  • shiftTo() can create a list of incremental positions along an arc and corresponding rotations and pass this list to llKeyFramedMotion()
However, llKeyFrameMotion() is a flawed implementation that drifts.  You will have to apply corrections when the motion completes, that is, if it completes.  The irksome details are documented in the caveats here.

See this future posting for the loop series fix.

Note 3:
Why A*B and not B*A?  First, for two combined rotations the order matters.  Second, this problem has two rotation: B = the prim rotation with respect to the region when closed (gRdoorShut = llGetPos()), and A = the addition rotation needed to turn the door from closed to open (llEuler2Rot(eulerRot)).

A is a rotation that is expressed in terms of the local coordinates of the prim that has already been rotated by B.   A is specifically a rotation about the prim local Y+ axis (swingAngleDeg).  In this case, where A is relative to B local coordinates, the multiplication order is A*B.

Note 4: 
The hinge point has both a global (region coordinates) and local (prim coordinates) position value.  By definition a prim rotation converts a vector in a the prim local coordinates to a vector in the global coordinates relative to the prim center.  Therefore the global position of the hinge point is its rotated local vector added to the prim global position.

When the door is open

   gPhingePointOpen = gPdoorOpen + lPhingePoint * gRdoorOpen

and when the door closed is 

   gPhingePointShut = gPdoorShut + lPhingePoint * gRdoorShut

However the hinge does not change position, so the right hand expressions have the same value.  Now solve the equated right side expressions for gPdoorOpen.  Q.E.D.