Jump to content

llRotBetween - Odd Behavior


Wulfie Reanimator
 Share

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

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

Recommended Posts

I made a little "car trailer" (to give you an idea) -- an object that "attaches" to a point in the world and follows it as if dragged around.

Problems started cropping up after I tried taking height differences into account. The follower starts rotating uncontrollably when it becomes tilted, sometimes being able to recover on its own, other times getting permanently "stuck" in a bad orientation -- seemingly always near-vertical. Here's some footage of me forcing the follower in bad spots.

I've noted that the follower is most stable (and able to recover) while facing towards the world X axis, which is the local axis that's meant to face toward the target.

Here's the code; it doesn't do anything special besides calculating the "head" position, calling llRotBetween, and updating its position/rotation.

default
{
    touch_start(integer n)
    {
        key target = "3d83a692-ffec-fb34-f43e-471d1958a262";
        float head_distance = 0.85;

        while(TRUE)
        {
            list data = llGetObjectDetails(target, (list)OBJECT_POS);
            vector target_pos = llList2Vector(data, 0);

            vector head_direction = <head_distance, 0, 0>;
            vector forward = llVecNorm(head_direction) * llGetRot();

            vector offset = target_pos - llGetPos();
            rotation new_rot = llRotBetween(forward, llVecNorm(offset));

            float distance_correction = llVecDist(llGetPos(), target_pos) - head_distance;

            llSetLinkPrimitiveParamsFast(LINK_ROOT,[
                PRIM_ROTATION, new_rot * llGetRot(),
                PRIM_POSITION, llGetPos() + ((forward * new_rot) * distance_correction)
            ]);

            llSleep(0.022);
        }
    }
}

Ideally, the follower should be able to roll, but no more than 90 degrees. Any ideas on how to keep this thing upright like llLookAt? I could of course use llLookAt (which doesn't allow any rolling), but I'm mainly interested in this problem and I'd prefer to set the rotation within SLPPF since I'm going to need to do all of these calculations for the position anyway.

P.S. I have seen the user-functions on llLookAt's wiki page, but they're running into math errors, so they aren't an option.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

I'd be tempted to fiddle around with the Z-component of new_rot and limit it to be within Pi/4 radians either side of the vertical position, possibly even less, so that first of all you'll be stopping it tilting too much, secondly, should you actually want the trailer to eventually fall over (assuming the towing vehicle has capsized) it would do so in several stages, sot of simulating inertia?

It's a fudge, I know

ETA

The tumbling behaviour is quite bizarre, it's almost as if it has several choices of the shortest distance and is flipping between them?

Edited by Profaitchikenz Haiku
Link to comment
Share on other sites

17 minutes ago, Profaitchikenz Haiku said:

I'd be tempted to fiddle around with the Z-component of new_rot and limit it to be within Pi/4 radians either side of the vertical position, possibly even less, so that first of all you'll be stopping it tilting too much, secondly, should you actually want the trailer to eventually fall over (assuming the towing vehicle has capsized) it would do so in several stages, sot of simulating inertia?

It's a fudge, I know

Clamping the angle(s) to PI/4 didn't make it more stable, and in the case of PI/8 it caused the object's position to sometimes deviate away wildly (caused by compounding errors due to the restricted turn-angle).

I'm not concerned about the trailer having to capsize, it's enough for me that it stays within 90 degrees of the world's Z axis. If that part could be solved, I think I'd be able to build upon it from there if I needed.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

firstly, you should clean up some unnessessary vecnorms in there.

1 hour ago, Wulfie Reanimator said:

vector head_direction = <head_distance, 0, 0>; vector forward = llVecNorm(head_direction)

will always set forward to <1,0,0> or <0,0,0> in the very specific case in which head_distance is 0.

 

secondly, rot between doesn't do what you are expecting it to. If you're in the domain where an object's roll is in consideration, rot between will always mess it up in hard to correct ways.

Better to just set the object's rotation to a specific Yaw Pitch and Roll if that's what you're after:

rotation YPR2Rot(vector v) // Yaw Pitch Roll
{   return
        <llSin(v.z/2),0,0,llCos(v.z/2)> *
        <0,llSin(v.y/2),0,llCos(v.y/2)> *
        <0,0,llSin(v.x/2),llCos(v.x/2)> ;
}

default
{
    touch_start(integer n)
    {
        key target = "3d83a692-ffec-fb34-f43e-471d1958a262";
        vector correction = <0,0,0.85>;

        while(TRUE)
        {
            list data = llGetObjectDetails(target, [OBJECT_POS]);
            vector pos = llGetPos();
            vector t_pos = llList2Vector(data, 0)+correction-pos;
            // ^^ vector from our pos to target's head.

            float yaw = llAtan2(t_pos.y,t_pos.x);
            float pitch = -llAtan2(t_pos.z,llVecMag(<t_pos.x,t_pos.y,0>));
            float roll = 15.0 * DEG_TO_RAD; // as an example.
            rotation new_rot = YPR2Rot(<yaw,pitch,roll>);

            llSetLinkPrimitiveParamsFast(LINK_ROOT,[
                PRIM_ROT_LOCAL, new_rot
            ]);
            // N.B never set a rotation using PRIM_ROTATION. it's borked.
            llSleep(0.022);
        }
    }
}

will do what you expect if you build your object such that it is facing the forward x direction.

for any other facing, you can rotate that facing to x before applying the YPR, so

llSetLinkPrimitiveParamsFast(LINK_ROOT,[
    PRIM_ROT_LOCAL, llRotBetween(<0,0,1>,<1,0,0>)*new_rot
]);

would replicate the llLookAt() behavior. (without damping of course)

Edit: I just made it a look at thing to make the concept clearer, you can figure out how to add back in the moving forward and the calculation of the roll yourself.

Edited by Quistessa
Link to comment
Share on other sites

I just realised it's not the Z-component you have to to clamp, it's the other tow. It's rotating around the Z axis to follow the the towing object, and then about the X and Y to correct for the height differences, they're the ones that possibly need to be constrained within limits. 

Also, reading what Quistessa's just posted, I think it's this "shortest angle between the two positions"  that's generating the tumbling.

 

ETA Ignore some of what I said above :)

Edited by Profaitchikenz Haiku
  • Like 1
Link to comment
Share on other sites

2 hours ago, Quistessa said:

firstly, you should clean up some unnessessary vecnorms in there.

3 hours ago, Wulfie Reanimator said:

vector head_direction = <head_distance, 0, 0>; vector forward = llVecNorm(head_direction)

will always set forward to <1,0,0> or <0,0,0> in the very specific case in which head_distance is 0.

To explain that that specific part, head_direction wasn't/may not necessarily be axis-aligned (but let's not worry about that). It's also the follower's head, not the target's.

2 hours ago, Quistessa said:

Better to just set the object's rotation to a specific Yaw Pitch and Roll if that's what you're after:

First of all, thanks. This is promising.

There's one (maybe two) problems though, first being that I don't seem to be able to get this to work. I only made a few changes here:

...vector correction = <0.85,0,0>; // changed: X instead of Z

...vector t_pos = llList2Vector(data, 0)-(pos+correction);
   // ^^ changed: vector from OUR HEAD to target.

...float roll = 0.0 * DEG_TO_RAD; // changed: 0

...which isn't accurate yet because correction isn't taking the follower's rotation into account, meaning it's always aiming from the wrong spot. Easy fix, let's just tweak t_pos into (pos+(correction*llGetRot))... oh god! I've no idea what causes that. And since I'm less comfortable with Atan2 than vectors/rotations, I'm not sure how to change my position code anymore. I can sleep on this though to clear my head.

Secondly, I may have explained it poorly, but by "rolling" I mean something like a boat banking in the water as it turns. As the follower is pulled to another position, I would like it to be able to "roll" around its X axis (the change doesn't need to be smooth). Your function sets the angle explicitly, which makes sense and isn't the worst problem as I can just fudge the angle based on the horizontal angle/yaw. Your method is a big step in a better direction but still very similar to llLookAt in the sense that I'll have to do more mathy things explicitly.

Also, how exactly is PRIM_ROTATION broken? I haven't noticed any issues with it.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

1 hour ago, Wulfie Reanimator said:

which isn't accurate yet because correction isn't taking the follower's rotation into account, meaning it's always aiming from the wrong spot.

Ahh, I had kinda skimmed the original post and assumed you wanted a variation of a thing that looks at people's heads. if you want correction to be based on the local reference frame of the target (for example, 1 meter behind no matter what direction they are facing) then you just multiply by the rotation of the target:

rotation target_rot = llList2Rot(llGetObjectDetails(target_key,[OBJECT_ROTATION]),0);
vector correction = <-1,0,0>*target_rot;

- - -

Ahh sorry, if I'm reading correctly the thing that's doing the looking is not the root but a child prim? maybe I shouldn't try and confuse things too much untill I have a better understanding of what is trying to look at what.

- - -

 

Edited by Quistessa
Link to comment
Share on other sites

33 minutes ago, Quistessa said:

Ahh sorry, if I'm reading correctly the thing that's doing the looking is not the root but a child prim? maybe I shouldn't try and confuse things too much untill I have a better understanding of what is trying to look at what.

I'm just gonna keep using the car trailer analogy.

The script is in the root prim. The root is located near the end of the trailer, like back-wheels that cannot turn.

The front of the trailer is attached to something, such as the back (target) of a car. You can imagine that the front of the trailer is lifted off the ground such that there isn't any friction.

When the "car" moves forward, it pulls on the front of the trailer, which is the essential effect I'm going for. This is why I had head_distance, which is meant to be an offset (from the root to the head of the trailer) where it attaches to something else. In the simplest case (what I've shown), it's directly opposite of the root.

If the car starts turning left, the trailer will naturally follow, not linearly but rather as you'd expect a car trailer to handle. (Collisions don't matter, the trailer can be allowed to clip into the car in front.) The same should apply going up or down. Being able to roll/bank (as if one side of the trailer was being lifted by uneven ground) would be a plus.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

23 minutes ago, Wulfie Reanimator said:

better explanation

In that case, I would put the root prim between the back wheels of the trailer, and rotate the trailer as in my first example, but with no offset (assuming the next trailer in the line also has its root in between the two back wheels). and set the position to <position of car thing this is hitched to>+(<desired trailing distance>*llVecNorm(<our pos>-<pos of car thing>))

  • Thanks 1
Link to comment
Share on other sites

Found an easy way to fake the roll, but wanted to do some inworld testing to make sure it actually worked:

rotation YPR2Rot(vector v) // Yaw Pitch Roll
{   return
        <llSin(v.z/2),0,0,llCos(v.z/2)> *
        <0,llSin(v.y/2),0,llCos(v.y/2)> *
        <0,0,llSin(v.x/2),llCos(v.x/2)> ;
}
default
{
    touch_start(integer n)
    {
        //key target = "3d83a692-ffec-fb34-f43e-471d1958a262";
        key target = "68686474-391b-44ea-b68f-9b88a566af9f";
        float follow_dist = 0.75;

        while(TRUE)
        {
            list data = llGetObjectDetails(target, [OBJECT_POS,OBJECT_ROT]);
            vector pos = llGetPos();
            vector t_pos = llList2Vector(data, 0)-pos;
            // ^^ vector from our pos to target.
            // also our ideal forward axis.

            float yaw = llAtan2(t_pos.y,t_pos.x);
            float pitch = -llAtan2(t_pos.z,llVecMag(<t_pos.x,t_pos.y,0>));
            vector t_fwd = llRot2Fwd(llList2Rot(data,1)); // forward axis of target
            float roll = 1.0 * (yaw-llAtan2(t_fwd.y,t_fwd.x));
            //increase/decrease 1.0 factor for more or less rolling.
            //also note, assumes hitch object will always move forward along its local x axis.
            rotation new_rot = YPR2Rot(<yaw,pitch,roll>);

            t_pos = llList2Vector(data, 0);
            //^^ repurpose as actual target position.

            llSetLinkPrimitiveParamsFast(LINK_THIS,[ // LINK_ROOT fails for single prim objects.
                PRIM_ROT_LOCAL, new_rot,
                PRIM_POSITION, t_pos + follow_dist*llVecNorm(pos-t_pos)
            ]);
            // N.B never set a rotation using PRIM_ROTATION. it's borked.
            llSleep(0.022);
        }
    }
}

or in other words, set the roll to the the difference in yaw between the two objects. (llAtan2(v.y,v.x) more or less calculates the yaw of vector v)

Edited by Quistessa
used yaw in roll calculation since we already computed it.
  • Thanks 1
Link to comment
Share on other sites

13 hours ago, Quistessa said:

in other words, set the roll to the the difference in yaw between the two objects. (llAtan2(v.y,v.x) more or less calculates the yaw of vector v)

Thank you! It's working perfectly.

I'm still puzzled by how llRotBetween had such erratic behavior, I understand the premise of "shortest rotation" but not why only positive X-axis-aligned rotations were stable unlike Y, -Y, or -X. I would've expected it to function more or less like the code above, without restricted roll angle.

Edited by Wulfie Reanimator
Link to comment
Share on other sites

6 minutes ago, Wulfie Reanimator said:

I'm still puzzled by how llRotBetween had such erratic behavior,

the short answer is because you were using it wrong in your original example. if you have something symmetrical about an axis (like a cone around the z axis)

llSLPPF(LINK_THIS,
[	PRIM_ROT_LOCAL, llRotBetween(<0,0,1>,PosOfTarget-llGetPos())
];

works fine (or it should, too lazy to do in-world debugging). or divide the rotation by llGetRootRotation() for a linked independently looking prim.

when working with Rot between it's best to visualize both vectors pointing out from a single point (the vectors do not need to be normalized by the way) RotBetween then spits out a rotation that when applied to an object rotates it from facing the first vector to facing the second.

If you are directly setting the rotation you need to keep in mind that you don't rot between your current forward vector and your target vector, but between your ideal forward vector (the forward vector the object would have if it were at ZERO_ROTATION) and the vector pointing from the object to the target (does not need to be normalized) In your first example I think you're calculating a difference and applying that to your current rotation ,which isn't as accurate as recalculating the correct rotation and setting that explicitly.

 

Link to comment
Share on other sites

2 hours ago, Quistessa said:

when working with Rot between it's best to visualize both vectors pointing out from a single point (the vectors do not need to be normalized by the way) RotBetween then spits out a rotation that when applied to an object rotates it from facing the first vector to facing the second.

I think(?) I understand this, that's how I've used the function in the past (successfully) and tried to in the original code I posted.

bdb8ccd1cd.png

In this case, the "trailer" or looking object's "forward" axis is almost vertical (<0.0, 0.3, 0.9>), and I wanted to align it toward the target (t_pos - pos = <0, 1, 0>). That's why in my code, I had "llRotBetween(forward, llVecNorm(offset))" which, assuming my understanding isn't flawed, should be able to calculate the correct rotation.

P.S. The reason why I am normalizing both vectors is because of this caveat for llRotBetween:

  • start * llRotBetween(start, end) == end is only true if start and end have the same magnitude and neither have a magnitude of zero

P.P.S. Now that I'm debugging the result more specifically, it seems like I was doing at least something correctly. If I dump llRot2Axis and llRot2Angle, I get the results "67.5 degrees around <-1, 0, 0>", which is correct even from just eyeballing the screenshot, but I can also prove it visually:

7573a6be45.png

The thin red cylinder is the axis of rotation, and the red cone is a cylinder with path-cut set to 0.1875 (65.7 / 360). This would indicate that the problem isn't in the calculations but how I'm applying them (since that's the only thing that happens after this point)?

...and I've just solved the mystery. In the original code I posted, I was applying the rotation as new_rot * llGetRot() when I should've used llGetRot() * new_rot.

// Minimal implementation
default
{
    touch_start(integer n)
    {
        key target = "dde34eec-b65f-5f3d-50ed-0f831df0ece5";

        while (TRUE)
        {
            list data = llGetObjectDetails(target, (list)OBJECT_POS);
            vector target_pos = llList2Vector(data, 0);

            vector forward = <1,0,0> * llGetRot();
            vector offset = target_pos - llGetPos();
            rotation new_rot = llRotBetween(forward, llVecNorm(offset));

            llSetLinkPrimitiveParamsFast(LINK_THIS,[
                PRIM_ROT_LOCAL, llGetRot() * new_rot
            ]);

            llSleep(0.022);
        }
    }
}

TLDR: Foiled by rotations again!

Edited by Wulfie Reanimator
Link to comment
Share on other sites

17 minutes ago, Wulfie Reanimator said:

rotation new_rot = llRotBetween(forward, llVecNorm(offset));

I've said it three or four times and I'll say it again. If you take out the llVecNorm you will get exactly the same result. the caveat you quoted  is technically true, but misleading.

Otherwise, good job debugging. I still think finding the direct rotation is more intuitive than finding the difference between current and desired rotation, but to each their own.

Not entirely related, but it might help demonstrate what llRotBetween does :

float uAngleBetween(vector a,vector b, vector c) // find the angle defined by the 3 points abc, with b as the pivot.
{	return llRot2Angle(llRotBetween(c-b,a-b));
}

https://external-content.duckduckgo.com/iu/?u=http%3A%2F%2Ffactfile.org%2Fwp-content%2Fuploads%2F2015%2F03%2Fangle-ABC.jpg&f=1&nofb=1

  • Thanks 1
Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 1064 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...