Jump to content

Finding the signed angle between 2 objects


Monica Balut
 Share

You are about to reply to a thread that has been inactive for 4724 days.

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

Object A is the reference object.  It views the world in the direction of its local positive x axis.  Object B will be in its periphery.  I eventually want to rotate object A so it points to object B amd I'm trying to do this at a constant speed of rotation  For a variety of reasons llRotLookAt won't work so I'm looking for a work around (see my previous post:

llRotLookAt while controlling rotation speed.

.  The algorithm I created almost works.  At one point I use llAngleBetween to calculate the angle between them.  However llAngleBetween does not provide the sign of the rotation.

I'm stuck trying to find a way that wil give me the sign of the angle that I must use to rotate object A so that it rotates clockwise or counter clockwise.

 

BTW thanks to Void Singer for pointing me in the right direction to start this.

 

Let me try to show you the code so far.  It does produce a nice smooth rotation

float SLICESIZE = 2.0; //degrees to be rotated at each step
float TIMERPERIOD = 0.1;


//globals

float   radianSlice;
integer nSteps;
rotation remainderRot;
float rotSign;
rotation incRot;
integer index;

RotateObject(vector targetPos)

{
        vector discPos = llGetPos();
        vector relativePos = targetPos - discPos; //-- make the target position relative to us
        relativePos = llVecNorm( <relativePos.x, relativePos.y,0.0> ); //in world coordinates-- remove the height difference and convert to unit vector.  This constrains the rotation to be around the z axis

//********This is where I'm stuck.  Since relativePos gives a vector in world coordinates, this give wrong results for rotSign below.  What I need here is to find the sign of the angle betweewn the local forward (x) facing vector of object A and the target.  With that, the code would work find.  I just can't wrap my head around how to do this.

        if ((relativePos.y) >= 0.0) rotSign = 1.0; // do rotation clockwise
        if (vectorAngle >= 0.0) rotSign = 1; // do rotation clockwise
            else rotSign = -1.0;
       
        rotation desiredRot = llRotBetween( <1.0, 0.0, 0.0>, relativePos); //-- get the rotation that faces our target
        float radiansBetween = rotSign * llAngleBetween( llGetRot(), desiredRot ); //-- angle between current rotation and target rotation
        float degreesBetween = radiansBetween * RAD_TO_DEG;
        nSteps = llAbs((integer) (radiansBetween / radianSlice));
        float remainderAngle = radiansBetween - nSteps*rotSign*radianSlice;
        incRot = llAxisAngle2Rot(<0.0,0.0,1.0> , radianSlice*rotSign);
        remainderRot = llAxisAngle2Rot(<0.0,0.0,1.0> , remainderAngle*rotSign);
        index = 1;
        llSetTimerEvent(TIMERPERIOD);
    }



default
{

state_entry()
    {
      lc = llListen(CHANNEL,"","","");
      radianSlice = (SLICESIZE * DEG_TO_RAD);

     RotateObject(SOME TARGET VECTOR);
    }

   
    timer()
    {
        if (index <= nSteps)
        {
            llSetLinkPrimitiveParamsFast(1, [PRIM_ROTATION, llGetRot()*incRot]);
            index ++;
        }
        else
        {
            llSetLinkPrimitiveParamsFast(1, [PRIM_ROTATION, llGetRot()*remainderRot]);
            llSetTimerEvent(0.0);
        }
    }

}

Link to comment
Share on other sites

I hate it when people post puzzles just before bedtime.  :smileyvery-happy:  I don't have the answer to yours, mostly because I really do have to get to bed and can't spend any more time playing with it.  Try this little script, though, and see if it helps point you in a productive direction .........

default{    touch_start(integer total_number)    {        llSensor("",llGetOwner(),AGENT,20,PI);    }        sensor(integer num)    {        vector gPos = llGetPos();        vector dPos = llDetectedPos(0);        rotation Rot = llRotBetween(<1.0,0.0,0.0>,llVecNorm(<dPos.x,dPos.y,gPos.z> -gPos));        rotation temp = Rot/llGetRot();        if(((temp.z >0.0) && (temp.s > 0)) || ((temp.z < 0.0)&& (temp.s < 0.0)))        {            llSay(0,(string)(Rot/llGetRot()) + " Counter-clockwise");        }        else        {            llSay(0,(string)(Rot/llGetRot()) + " Clockwise");        }        llRotLookAt(Rot,1.0,0.2);    }}

 

Link to comment
Share on other sites

SLERP to the rescue.....

use current rotation for the first value, target rotation for the second value, and (step / angle) for the third.

this will give you the single step value that's automaticaly signed. then keep applying that rotation (integer)(angle / step) times in a loop and as a final step after the loop set the target rotation

Link to comment
Share on other sites

Thanks for your input.  Here's what I was able to come up with.  The idea is a llRotLookAt clone that rotates the object at a constant speed thru the smallest angle.  It's not pretty but it works.  I would welcome any rotation gurus to have a shot at simplifying it and cleaning it up and perhaps post it to the library.

//This is set up so touching the target causes the target to say its position on channel -45

integer CHANNEL = -45;
float SPEED = 0.4;  //degrees per sec
float TIMERPERIOD = 0.1;

integer lc;
float   radianSlice;
integer nSteps;
rotation incRot;
rotation remainderRot;
integer index;

MyRotLookAtConstantRotSpeed(vector targetPos)
{
    vector discPos = llGetPos(); // postition of the object to be rotated, in this case an object whose root prim is a disc
    vector relativePos = targetPos - discPos; //-- make the target position relative to us
    relativePos = llVecNorm( <relativePos.x, relativePos.y,0.0> ); //in world coordinates-- remove the height difference and convert to unit vector
    rotation desiredRot = llRotBetween( <1.0, 0.0, 0.0>, relativePos); //-- get the rotation that faces our target
    vector eulerDiscRot = llRot2Euler(llGetRot()); // get the disc's current world rotation in a vector form
    vector eulerDesiredRot = llRot2Euler(desiredRot); // the target's world rotation in vector form
    float radiansBetween = eulerDesiredRot.z - eulerDiscRot.z; //-- signed angle between current rotation and target rotation.
    if (radiansBetween > PI ) radiansBetween = radiansBetween - 2*PI;
    else if (radiansBetween < -PI) radiansBetween = 2*PI + radiansBetween; // this keeps the rotation angle < PI and gets the correct sign
    radianSlice = ((SPEED / TIMERPERIOD) * DEG_TO_RAD);  // absolute value of radians to be turned at every inc.  Rotation direction will be handled below;
    if (radiansBetween < 0.0) radianSlice = - radianSlice;
    nSteps = (integer) (radiansBetween / radianSlice);
    incRot = llAxisAngle2Rot(<0.0,0.0,1.0> , radianSlice);  // the rotation to be done at each timer step
    float remainderAngle = radiansBetween - nSteps*radianSlice; // the last angle to be done when it's almost there
    remainderRot = llAxisAngle2Rot(<0.0,0.0,1.0> , remainderAngle); // convert to a rotation around z
    nSteps = llAbs(nSteps);
    index = 1;
    llSetTimerEvent(TIMERPERIOD);
}

default
{
    state_entry()
    {
      lc = llListen(CHANNEL,"","","");
    }
   
    listen(integer channel, string name, key id, string msg)
    {
        vector targetPos = (vector) msg;
        MyRotLookAtConstantRotSpeed(targetPos);
    }
   
    timer()
    {
        if (index <= nSteps)
        {
            llSetLinkPrimitiveParamsFast(1, [PRIM_ROTATION, llGetRot()*incRot]);
            index ++;
        }
        else
        {
            llSetLinkPrimitiveParamsFast(1, [PRIM_ROTATION, llGetRot()*remainderRot]);
            llSetTimerEvent(0.0);
        }
    }
}

Link to comment
Share on other sites

here's what I got, breaking it all down to essentials, and inserting a slight modification of the Slerp algorithm... all completely untested, but once I do I'll insert the universal versions on the wiki....

first your case, with a global position and a rate of movement in radians per second (just multiply degrees per second by DEG_TO_RAD)....

uSteppedRotLookAtCustomOnlyZ( vector vPosTarget, float vFltRadPerSec ){	vPosTarget   = vPosTarget - llGetLocalPos(); //-- localize vector to us	vPosTarget.z = 0.0;                          //-- remove z to prevent looking up or down	rotation vRotTarget = llRotBetween( ,  vPosTarget ); //-- find parent level rotation target	//-- divide angle between rots by rate divided by 5 (for the .2sec delay in the function), to get steps (reused rate)	if ((integer)vFltRadPerSec = llAcos( (vPosTarget = llVecNorm( vPosTarget )) * 	                                     ( * llGetLocalRot()) ) / (vFltRadPerSec / 5.0 )){		//-- use inverse of steps in modified slerp algorithm to get the step size		rotation vRotStep = llAxisAngle2Rot( llRot2Axis( vRotTarget / llGetLocalRot() ),		                    (1.0 / vFltRadPerSec) * llRot2Angle( vRotTarget / llGetLocalRot() ) );		vFltRadPerSec = (integer)vFltRadPerSec; //-- integerize the steps so the loop doesn't break		do{ //-- loop adding the step rotation to the current rotation			llSetLocalRot( vRotStep * llGetLocalRot() );		}while( --vFltRadPerSec );	}//-- next line take care of any fractional remainder of the steps	llSetLocalRot( vRotTarget );}

 

next up is a more generalized version that's appropriate for local position coordinates, you'll note the only real difference is two lines missing...

//-- vPosTarget as a vector local from current positionuSteppedRotLookAt( vector vPosTarget, float vFltRadPerSec ){	rotation vRotTarget = llRotBetween( ,  vPosTarget );	if ((integer)(vFltRadPerSec = llAcos( (vPosTarget = llVecNorm( vPosTarget )) *	                                      ( * llGetLocalRot()) ) / (vFltRadPerSec / 5.0))){		rotation vRotStep = llAxisAngle2Rot( llRot2Axis( vRotTarget / llGetLocalRot() ),		                    (1.0 / vFltRadPerSec) * llRot2Angle( vRotTarget / llGetLocalRot() ) );		vFltRadPerSec = (integer)vFltRadPerSec;		do{			llSetLocalRot( vRotStep * llGetLocalRot() );		}while( --vFltRadPerSec );	}	llSetLocalRot( vRotTarget );}

 

and thirdly we have it stripped down to take a rotation target, similar to the original function...

uSteppedRotLookAt( rotation vRotTarget, float vFltRadPerSec ){	if ((integer)(vFltRadPerSec = (llAngleBetween( llGetLocalRot(), vRotTarget ) / (vFltRadPerSec / 5.0)))){		rotation vRotStep = llAxisAngle2Rot( llRot2Axis( vRotTarget / llGetLocalRot() ),		                    (1.0 / vFltRadPerSec) * llRot2Angle( vRotTarget / llGetLocalRot() ) );		vFltRadPerSec = (integer)vFltRadPerSec;		do{			llSetLocalRot( vRotStep * llGetLocalRot() );		}while( --vFltRadPerSec );	}	llSetLocalRot( vRotTarget );}

 

and lastly, because non of those are interruptable, I'll demonstrate the integration to a timered script snippet, which minimally exports the active portions to the timer...

//-- required globalsrotation gRotTarget; //-- target now globalrotation gRotStep;   //-- rot step now globalinteger  gIntStep;   //-- loop counter now it's own variable//-- required function code (code be inserted into an event)uSteppedRotLookAt( rotation vRotTarget, float vFltRadPerSec ){	gRotTarget = vRotTarget; //-- export the target rotation to global for use later	//-- if statement stays the same	if ((integer)(vFltRadPerSec = (llAngleBetween( llGetLocalRot(), vRotTarget ) / (vFltRadPerSec / 5.0)))){		gRotStep = llAxisAngle2Rot( llRot2Axis( vRotTarget / llGetLocalRot() ), //-- rot step now saved globally		           (1.0 / vFltRadPerSec) * llRot2Angle( vRotTarget / llGetLocalRot() ) );		gIntStep = (integer)vFltRadPerSec; //-- step counter now saved globally	}else{ //-- added else		gIntStep = 0; //-- captures fractional steps below 1	}	llSetTimerEvent( 0.2 ); //-- never change, this is the delay in the set local rot function}//-- somewhere in the state...	timer(){		if (gIntStep--){ //-- loop test becomes if, step decrement turn from pre to post because of position change			llSetLocalRot( gRotStep * llGetLocalRot() ); //-- moved from the old loop		}else{ //-- else captures what happened after the original loop			llSetLocalRot( gRotTarget ); //-- moved from function end			llSetTimerEvent( 0.0 ); //-- stop the timer since we're at would have been the function end		}	}

 that last integration works for all three...

 

ETA:
Never EVER use a float variable for a loop counter  testing for a single value unless A) you only modify it by powers of 2, (2^0 is valid) and B) it's not going to exceed the limit of reliable integers in a float (about 7 digits)....

I'll be adding a lower minimum for radians/second one I figure out what that limit is. there is no upper limit for this function other than realistically >= 5.0*PI which will cause it to just snap to any input target  rotation.

ETA2:
tested and corrected an oversight, minimum value for vFltRadPerSec is ~0.00000003 (~0.00002 degrees), which is so small and slow that you should never ever even want to use anything that small.

Link to comment
Share on other sites

you can also use sleeps and the timered version, or calculate how low SLPPF runs.... 5 is the scaling factor for the loop time (1 / loop_time = scale_factor) so you can rescale it for whatever you choose to use.

minimum values for rate are calculated as ((1 / 16,777,215)  * scale_factor) radians (* RAD_TO_DEG for degrees)

max value is (PI * scale_factor) radians or (180 * scale_factor) degrees

16.777,215 is 24 bits, the maximum reliable integer that can be expressed as a float if you're curious

 

as to why? after testing set local at 20deg/sec and being barely able to notice jitter, I decided it was good enough... lower loop times are possible with SLPPF but the similar jitter was noticed in cycles (due to the nature of script execution frames) so I figured regular jitter was better than cyclic jitter for a fixed rate.

Link to comment
Share on other sites


Void Singer wrote:

as to why? after testing set local at 20deg/sec and being barely able to notice jitter, I decided it was good enough... lower loop times are possible with SLPPF but the similar jitter was noticed in cycles (due to the nature of script execution frames) so I figured regular jitter was better than cyclic jitter for a fixed rate.

Jitter will appear differently on different viewer/viewer settings

The viewer has a built-in smoothing when showing an object position shift

It does not just show the two positions but a move from one to the other

What looks good on one viewer may not look good on all viewers:smileysurprised:

Link to comment
Share on other sites

I think we're talking about two different things here. You're referring to the amplitude (how smooth the transitions from one position to another are) which indeed does vary with settings (different types of interpolation can help here), and should be stable for each viewer, even if it's not the same between different setups. There shouldn't be any amplitude jitter unless the video card is struggling, which is outside the control of the script itself.

I'm talking about the frequency, how regularly the changes are. Ideally this should also be stable, but between the scripting schedulers time frame and time dilation (and even stability of net connection) this can suffer... regularity here is seen by the brain as a differnt kind of smoothness, and frequency jitter stands out and calls both amplitude and frequency into focus (because it changes the curve for amplitude).

all factors being equal, I saw more frequency jitter with SLPPF, I'm guessing because of frame breaks due to time dilation, because multiple calls might push through in a single frame, but then when the frame gets limited they feel like they come in bursts or waves.

Link to comment
Share on other sites

Yeah I guess.  I'm rotating a 8m disc at about 20 degrees per second and it looks choppy with llSetRot.  If the prim is much smaller it's not as noticeable.

I had always assumed that the 0.2 sec script delay would delay the triggering of the timer event as well.  From Void's code, I assume I was wrong about that.

Link to comment
Share on other sites

only if the code take longer to execute than the time till the next timer event gets queed, and then at worst it bumps it utill the current code is finished. tecnically the loop takes a hair longer that just the function call to repeat, but it's so close that it is almost not worth trying to figure out what that hairs breadth is worth in time for most applications...

if you ever really want to know you have to be on a region with zero other scripts and zero lag, then find the execution time... 1.0 / execution_time = scale_factor.

Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 4724 days.

Please take a moment to consider if this thread is worth bumping.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...