Difference between revisions of "LlRotBetween"

From Second Life Wiki
Jump to navigation Jump to search
m
m (Replace Template:SourceLink/bitbucket with SourceLink:github)
 
(20 intermediate revisions by 6 users not shown)
Line 6: Line 6:
|func_footnote
|func_footnote
|func_desc
|func_desc
|return_text=that is the shortest rotation between the direction {{LSLP|start}} and the direction {{LSLP|end}}
|return_text=that is the shortest rotation between the ''direction'' '''start''' and the ''direction'' '''end'''
|spec={{LSLP|start}} and {{LSLP|end}} are directions and are relative to the origin <0.0, 0.0, 0.0>. If you have coordinates relative to a different origin, subtract that origin from the input vectors.
|spec='''start''' and '''end''' are ''directions'' and are relative to the origin <0.0, 0.0, 0.0>. If you have coordinates relative to a different origin, subtract that origin from the input vectors.
 
'''Note that the input vectors are '''''directions''''', NOT position coordinates.'''
 
Feeding position coordinates will '''not''' work, or at minimum be very inaccurate and / or inconsistent.
|caveats=
|caveats=
*<code>start * llRotBetween(start, end) == end</code> is only true if {{LSLP|start}} and {{LSLP|end}} have the same magnitude and neither have a magnitude of zero (see [[#Useful Snippets]] for a workaround).
*<code>start * llRotBetween(start, end) == end</code> is only true if '''start''' and '''end''' have the same magnitude and neither have a magnitude of zero (see [[#Useful Snippets]] for a workaround).
**This of course is ignoring floating point precision errors.
**This of course is ignoring floating point precision errors.
* The above is true because of vector magnitudes and not a shortcoming of this function. The '''rotation''' returned is '''correct''' regardless of magnitudes
* Rotations are from -PI to +PI around each axis.
* Rotations are from -PI to +PI around each axis.
|constants
|constants
|examples=
|examples=
<lsl>llRotBetween(<1.0, 0.0, 0.0>, <0.0, -1.0, 0.0>)
Drop the below script into a prim. This script will cause the object to orient it's positive X axis towards it's owner's avatar when touched.
<syntaxhighlight lang="lsl2">
default
{
    touch_start(integer total_number)
    {
        list lTemp = llGetObjectDetails(llGetOwner(),[OBJECT_POS]); //Get the owner's position (region coordinates)
        vector start = llRot2Fwd(ZERO_ROTATION); //Object's X axis is forward. (Can be substituted with llRot2Left (Y axis) or llRot2Up (Z axis). Negative values (-llRot2Fwd) can be used to spin the prim 180 degrees)
        //start = llRot2Fwd( llAxisAngle2Rot(<0,0,1>,45*DEG_TO_RAD) ); //Uncommenting this line will cause the prim to point one of it's corners towards the avatar, instead of the forward face.
        vector end = llVecNorm(llList2Vector(lTemp,0) - llGetPos()); //Convert the owner's position into a normalized direction vector (relative to the prim).
        llSetLinkPrimitiveParamsFast(LINK_THIS,[PRIM_ROTATION,llRotBetween(start,end)]); //Set the prim's rotation accordingly.
    }
    //llRot2Fwd(ZERO_ROTATION) is equivalent to <1,0,0> , llRot2Left would be <0,1,0> and llRot2Up would be <0,0,1>. Negative values ( -llRot2Fwd(ZERO_ROTATION) ) would be <-1,0,0>, -llRot2Left would be <0,-1,0> and -llRot2Up would be <0,0,-1>
    //Note that a value other than ZERO_ROTATION may cause unexpected results. Unless you know the exact offset you need for your object, then leave this as-is.
} //Jenna Huntsman
</syntaxhighlight>
 
 
<syntaxhighlight lang="lsl2">llRotBetween(<1.0, 0.0, 0.0>, <0.0, -1.0, 0.0>)
// will return <0.00000, 0.00000, -0.70711, 0.70711> (which represents -90 degrees on the z axis)
// will return <0.00000, 0.00000, -0.70711, 0.70711> (which represents -90 degrees on the z axis)


llRotBetween(<0.0, 0.0, 0.0>, <0.0, -1.0, 0.0>)
llRotBetween(<0.0, 0.0, 0.0>, <0.0, -1.0, 0.0>)
// will return <0.00000, 0.00000, 0.00000, 1.00000> (which represents a zero angle on all axis)
// will return <0.00000, 0.00000, 0.00000, 1.00000> (which represents a zero angle on all axis)
// because <0.0, 0.0, 0.0> does not convey a direction.</lsl>
// because <0.0, 0.0, 0.0> does not convey a direction.</syntaxhighlight>
|helpers
|helpers
|also_functions={{LSL DefineRow||[[llAngleBetween]]}}
|also_functions={{LSL DefineRow||[[llAngleBetween]]}}
{{LSL DefineRow||[[llRot2Fwd]]}}
{{LSL DefineRow||[[llRot2Left]]}}
{{LSL DefineRow||[[llRot2Up]]}}
{{LSL DefineRow||[[llVecNorm]]}}
|also_tests
|also_tests
|also_events
|also_events
|also_articles
|also_articles
|helpers=This function adjusts the magnitude of the quaternion so <code>start * llRotBetween(start, end) == end</code> is true as long as neither have a magnitude really close to zero. They do not have to have the same magnitude. (If either is too close to zero than this will return an unadjusted quaternion). While this is mathematically correct, it won't help with floating point rounding errors, so it's more accurate to say <code>start * return ≈ end</code>.
|helpers=This function adjusts the magnitude of the quaternion so <code>start * llRotBetween(start, end) == end</code> is true as long as neither have a magnitude really close to zero. They do not have to have the same magnitude. (If either is too close to zero than this will return an unadjusted quaternion). While this is mathematically correct, it won't help with floating point rounding errors, so it's more accurate to say <code>start * return ≈ end</code>.
<lsl>rotation RotBetween(vector start, vector end) //adjusts quaternion magnitude so (start * return == end)  
<syntaxhighlight lang="lsl2">rotation RotBetween(vector start, vector end) //adjusts quaternion magnitude so (start * return == end)  
{//Authors note: I have never had a use for this but it's good to know how to do it if I did.
{//Authors note: I have never had a use for this but it's good to know how to do it if I did.
     rotation rot = llRotBetween(start, end);
     rotation rot = llRotBetween(start, end);
Line 34: Line 61:
             return <rot.x * d, rot.y * d, rot.z * d, rot.s * d>;
             return <rot.x * d, rot.y * d, rot.z * d, rot.s * d>;
     return rot;
     return rot;
}//Strife Onizuka</lsl>
}//Strife Onizuka</syntaxhighlight>
|notes=
|notes=
Vectors that are near opposite each other in direction may lead to erroneous results.  
Vectors that are near opposite each other in direction may lead to erroneous results.  
<lsl>
<syntaxhighlight lang="lsl2">
// First Vector is due north second vector is ALMOST due south.
// First Vector is due north second vector is ALMOST due south.
rotation lRotation = llRotBetween( <0., 1., 0.>, <-0.001, -.1, 0.> );
rotation lRotation = llRotBetween( <0., 1., 0.>, <-0.001, -.1, 0.> );
llSay(0, lRotation );
llSay(0, lRotation );
// Provides a result of <1.00000, 0.00000, 0.00000, 0.00000>.
// Provides a result of <1.00000, 0.00000, 0.00000, 0.00000>.
</lsl>
</syntaxhighlight>
|location={{SourceLink|indra/llmath/llquaternion.cpp|line=429}}
|location={{SourceLink/github|viewer|indra/llmath/llquaternion.cpp|line=425}}
|deepnotes= === Replacement ===
|deepnotes= === Replacement ===
Due to the annoying quirks of this function {{User|Moon Metty}} wrote a drop in replacement. - {{Jira|SCR-309}}.
Due to the annoying quirks of this function {{User|Moon Metty}} wrote a drop in replacement. - {{Jira|SCR-309}}.
The maximum error is reported to be 2.7e-7 @ 2.2 radians.
The maximum error is reported to be 2.7e-7 @ 2.2 radians.
<lsl>rotation RotBetween(vector a, vector b)
<syntaxhighlight lang="lsl2">rotation RotBetween(vector a, vector b)
{
{
     float aabb = llSqrt((a * a) * (b * b)); // product of the lengths of the arguments
     float aabb = llSqrt((a * a) * (b * b)); // product of the lengths of the arguments
     if (aabb)
     if (aabb)
     {
     {
         float ab = (a * b) / aabb; // normalized dotproduct of the arguments
         float ab = (a * b) / aabb; // normalized dotproduct of the arguments (cosine)
         vector c = (a % b) / aabb; // normalized crossproduct of the arguments
         vector c = <(a.y * b.z - a.z * b.y) / aabb,
         float cc = c * c; // squared length of the crossproduct
                    (a.z * b.x - a.x * b.z) / aabb,
         if (cc) // test if the arguments are (anti)parallel
                    (a.x * b.y - a.y * b.x) / aabb >; // normalized crossproduct of the arguments
         float cc = c * c; // squared length of the normalized crossproduct (sine)
         if (cc) // test if the arguments are not (anti)parallel
         {
         {
             float s = 1.0; // set up the s-element with a PI/2 angle
             float s;
             if (ab * ab < cc) // compare the cosine squared to the sine squared
             if (ab > -0.707107)
                s += ab; // use the cosine to adjust the s-element
                 s = 1 + ab; // use the cosine to adjust the s-element
            else if (ab > 0.0) // test if the angle is smaller than PI/2
                 s += llSqrt(1.0 - cc); // use the sine to adjust the s-element
             else  
             else  
                 s -= llSqrt(1.0 - cc); // use the sine to adjust the s-element, and restore the sign
                 s = cc / (1 + llSqrt(1 - cc)); // use the sine to adjust the s-element
             float m = llSqrt(cc + s * s); // the magnitude of the quaternion
             float m = llSqrt(cc + s * s); // the magnitude of the quaternion
             return <c.x / m, c.y / m, c.z / m, s / m>; // return the normalized quaternion
             return <c.x / m, c.y / m, c.z / m, s / m>; // return the normalized quaternion
         }
         }
         else if (ab < 0.0) // test if the angle is smaller than PI/2
         if (ab > 0)
             return <1.0, 0.0, 0.0, 0.0>; // the arguments are anti-parallel
            return ZERO_ROTATION; // the arguments are parallel, or anti-parallel if not true:
        float m = llSqrt(a.x * a.x + a.y * a.y); // the length of one argument projected on the XY-plane
        if (m)
             return <a.y / m, -a.x / m, 0, 0>; // return a rotation with the axis in the XY-plane
        return <1, 0, 0, 0>; // the arguments are parallel to the Z-axis, rotate around the X-axis
     }
     }
     // the arguments are too small or parallel, return zero rotation
     return ZERO_ROTATION; // the arguments are too small, return zero rotation
    return ZERO_ROTATION;
}//Written by Moon Metty, optimized by Strife Onizuka
}//Written by Moon Metty, minor changes by Strife Onizuka</lsl>
// This version keeps the axis in the XY-plane, in case of anti-parallel vectors (unlike the current LL implementation). -- Moon Metty</syntaxhighlight>


===Bad Reference Implementation===
===Bad Reference Implementation===
<lsl>//Loosely based on SL source code, like SL's version, it's not very accurate. You want to use the above version.
<syntaxhighlight lang="lsl2">//Loosely based on SL source code, like SL's version, it's not very accurate. You want to use the above version.
rotation llRotBetween(vector start, vector end) {
rotation llRotBetween(vector start, vector end) {
     vector v1 = llVecNorm(start);
     vector v1 = llVecNorm(start);
Line 83: Line 115:
     if (dot < -0.9999999) {
     if (dot < -0.9999999) {
         // 180 degrees or there abouts
         // 180 degrees or there abouts
         vector ortho_axis = llVecNorm(<1.f, 0.f, 0.f> - (sn * (sn.x / (sn * sn))));
         vector ortho_axis = llVecNorm(<1.f, 0.f, 0.f> - (v1 * (v1.x / (v1 * v1))));
         if (ortho_axis)
         if (ortho_axis)
             return < ortho_axis.x, ortho_axis.y, ortho_axis.z, 0.f>;
             return < ortho_axis.x, ortho_axis.y, ortho_axis.z, 0.f>;
Line 95: Line 127:
     float m = llPow((axis * axis) + (dot * dot), -0.5);
     float m = llPow((axis * axis) + (dot * dot), -0.5);
     return <axis.x * m, axis.y * m, axis.z * m, dot * m>;
     return <axis.x * m, axis.y * m, axis.z * m, dot * m>;
}</lsl>
}</syntaxhighlight>
===llRotBetween alternatives===
[[User:Dora_Gustafson/llRotBetween_alternatives#llRotBetween.2C_some_alternatives_and_considerations|Alternatives and considerations]]
|cat1=Math/3D
|cat1=Math/3D
|cat2=Rotation
|cat2=Rotation

Latest revision as of 14:11, 17 November 2022

Summary

Function: rotation llRotBetween( vector start, vector end );
0.0 Forced Delay
10.0 Energy

Returns a rotation that is the shortest rotation between the direction start and the direction end

• vector start
• vector end

Specification

start and end are directions and are relative to the origin <0.0, 0.0, 0.0>. If you have coordinates relative to a different origin, subtract that origin from the input vectors.

Note that the input vectors are directions, NOT position coordinates.

Feeding position coordinates will not work, or at minimum be very inaccurate and / or inconsistent.

Caveats

  • start * llRotBetween(start, end) == end is only true if start and end have the same magnitude and neither have a magnitude of zero (see #Useful Snippets for a workaround).
    • This of course is ignoring floating point precision errors.
  • The above is true because of vector magnitudes and not a shortcoming of this function. The rotation returned is correct regardless of magnitudes
  • Rotations are from -PI to +PI around each axis.

Examples

Drop the below script into a prim. This script will cause the object to orient it's positive X axis towards it's owner's avatar when touched.

default
{
    touch_start(integer total_number)
    {
        list lTemp = llGetObjectDetails(llGetOwner(),[OBJECT_POS]); //Get the owner's position (region coordinates)
        vector start = llRot2Fwd(ZERO_ROTATION); //Object's X axis is forward. (Can be substituted with llRot2Left (Y axis) or llRot2Up (Z axis). Negative values (-llRot2Fwd) can be used to spin the prim 180 degrees)
        //start = llRot2Fwd( llAxisAngle2Rot(<0,0,1>,45*DEG_TO_RAD) ); //Uncommenting this line will cause the prim to point one of it's corners towards the avatar, instead of the forward face.
        vector end = llVecNorm(llList2Vector(lTemp,0) - llGetPos()); //Convert the owner's position into a normalized direction vector (relative to the prim).
        llSetLinkPrimitiveParamsFast(LINK_THIS,[PRIM_ROTATION,llRotBetween(start,end)]); //Set the prim's rotation accordingly.
    }
    //llRot2Fwd(ZERO_ROTATION) is equivalent to <1,0,0> , llRot2Left would be <0,1,0> and llRot2Up would be <0,0,1>. Negative values ( -llRot2Fwd(ZERO_ROTATION) ) would be <-1,0,0>, -llRot2Left would be <0,-1,0> and -llRot2Up would be <0,0,-1>
    //Note that a value other than ZERO_ROTATION may cause unexpected results. Unless you know the exact offset you need for your object, then leave this as-is.
} //Jenna Huntsman


llRotBetween(<1.0, 0.0, 0.0>, <0.0, -1.0, 0.0>)
// will return <0.00000, 0.00000, -0.70711, 0.70711> (which represents -90 degrees on the z axis)

llRotBetween(<0.0, 0.0, 0.0>, <0.0, -1.0, 0.0>)
// will return <0.00000, 0.00000, 0.00000, 1.00000> (which represents a zero angle on all axis)
// because <0.0, 0.0, 0.0> does not convey a direction.

Useful Snippets

This function adjusts the magnitude of the quaternion so start * llRotBetween(start, end) == end is true as long as neither have a magnitude really close to zero. They do not have to have the same magnitude. (If either is too close to zero than this will return an unadjusted quaternion). While this is mathematically correct, it won't help with floating point rounding errors, so it's more accurate to say start * return ≈ end.

rotation RotBetween(vector start, vector end) //adjusts quaternion magnitude so (start * return == end) 
{//Authors note: I have never had a use for this but it's good to know how to do it if I did.
    rotation rot = llRotBetween(start, end);
    float d = start * start;
    if(d)//is 'start' zero?
        if((d = llPow(end * end / d, 0.25)))//is 'end' zero?
            return <rot.x * d, rot.y * d, rot.z * d, rot.s * d>;
    return rot;
}//Strife Onizuka

Notes

Vectors that are near opposite each other in direction may lead to erroneous results.

// First Vector is due north second vector is ALMOST due south.
rotation lRotation = llRotBetween( <0., 1., 0.>, <-0.001, -.1, 0.> );
llSay(0, lRotation );
// Provides a result of <1.00000, 0.00000, 0.00000, 0.00000>.

See Also

Functions

•  llAngleBetween
•  llRot2Fwd
•  llRot2Left
•  llRot2Up
•  llVecNorm

Deep Notes

Replacement

Due to the annoying quirks of this function Moon Metty wrote a drop in replacement. - SCR-309. The maximum error is reported to be 2.7e-7 @ 2.2 radians.

rotation RotBetween(vector a, vector b)
{
    float aabb = llSqrt((a * a) * (b * b)); // product of the lengths of the arguments
    if (aabb)
    {
        float ab = (a * b) / aabb; // normalized dotproduct of the arguments (cosine)
        vector c = <(a.y * b.z - a.z * b.y) / aabb,
                    (a.z * b.x - a.x * b.z) / aabb,
                    (a.x * b.y - a.y * b.x) / aabb >; // normalized crossproduct of the arguments
        float cc = c * c; // squared length of the normalized crossproduct (sine)
        if (cc) // test if the arguments are not (anti)parallel
        {
            float s;
            if (ab > -0.707107)
                s = 1 + ab; // use the cosine to adjust the s-element
            else 
                s = cc / (1 + llSqrt(1 - cc)); // use the sine to adjust the s-element
            float m = llSqrt(cc + s * s); // the magnitude of the quaternion
            return <c.x / m, c.y / m, c.z / m, s / m>; // return the normalized quaternion
        }
        if (ab > 0) 
            return ZERO_ROTATION; // the arguments are parallel, or anti-parallel if not true:
        float m = llSqrt(a.x * a.x + a.y * a.y); // the length of one argument projected on the XY-plane
        if (m) 
            return <a.y / m, -a.x / m, 0, 0>; // return a rotation with the axis in the XY-plane
        return <1, 0, 0, 0>; // the arguments are parallel to the Z-axis, rotate around the X-axis
    }
    return ZERO_ROTATION; // the arguments are too small, return zero rotation
}//Written by Moon Metty, optimized by Strife Onizuka
 
// This version keeps the axis in the XY-plane, in case of anti-parallel vectors (unlike the current LL implementation). -- Moon Metty

Bad Reference Implementation

//Loosely based on SL source code, like SL's version, it's not very accurate. You want to use the above version.
rotation llRotBetween(vector start, vector end) {
    vector v1 = llVecNorm(start);
    vector v2 = llVecNorm(end);
    float dot = v1 * v2;
    vector axis = v1 % v2;
    if (dot < -0.9999999) {
        // 180 degrees or there abouts
        vector ortho_axis = llVecNorm(<1.f, 0.f, 0.f> - (v1 * (v1.x / (v1 * v1))));
        if (ortho_axis)
            return < ortho_axis.x, ortho_axis.y, ortho_axis.z, 0.f>;
        return <0.0, 0.0, 1.0, 0.0>;
    }
    else if(dot > 0.9999999) {
        //parallel
        return ZERO_ROTATION;
    }
    dot = dot + 1.0;
    float m = llPow((axis * axis) + (dot * dot), -0.5);
    return <axis.x * m, axis.y * m, axis.z * m, dot * m>;
}

llRotBetween alternatives

Alternatives and considerations

Source

Signature

function rotation llRotBetween( vector start, vector end );