Quistess Alpha Posted May 6, 2021 Share Posted May 6, 2021 (edited) Inspired by a recent thread I've been working on a few examples of how to use quaternions and such for rotations. as a part of that it would be useful to have a function that is the inverse of llEuler2Rot, i.e. that takes a rotation and gives the SL-euler angle. I got a version working for ZYX intrinsic == XYZ extrinsic rotations (which is much more intuitive than SL's Euler angle implementation IMO), but not for XYZi/ZYXe rotations (the convention llEuler2Rot uses). vector uRot2Euler(rotation rot) // borked :( only works on single-axis rotations. { vector ret; vector temp = <0,1,0>*rot; ret.x = llAtan2(temp.z,temp.y); rot = rot/llEuler2Rot(<ret.x,0,0>); temp = <1,0,0>*rot; ret.y = -llAtan2(temp.z,temp.x); rot = rot/llEuler2Rot(<0,ret.y,0>); temp = <1,0,0>*rot; ret.z = llAtan2(temp.y,temp.x); return ret; } vector uRot2Euler2(rotation rot) { vector ret; vector temp = <1,0,0>*rot; ret.z = llAtan2(temp.y,temp.x); rot = rot/llEuler2Rot(<0,0,ret.z>); temp = <1,0,0>*rot; ret.y = -llAtan2(temp.z,temp.x); rot = rot/llEuler2Rot(<0,ret.y,0>); temp = <0,1,0>*rot; ret.x = llAtan2(temp.z,temp.y); return ret; } rotation uEuler2Rot2(vector v) { return //ZYX intrinsic = XYZ extrinsic <llSin(v.x/2),0,0,llCos(v.x/2)> * <0,llSin(v.y/2),0,llCos(v.y/2)> * <0,0,llSin(v.z/2),llCos(v.z/2)> ; } rotation uEuler2Rot(vector v) // duplicates llEuler2Rot() { return //XYZ intrinsic = ZYX extrinsic <0,0,llSin(v.z/2),llCos(v.z/2)> * <0,llSin(v.y/2),0,llCos(v.y/2)> * <llSin(v.x/2),0,0,llCos(v.x/2)> ; } default { state_entry() { rotation rot = //<0,0,0,1>; //< 0.5, -0.5, 0.5, 0.5 >; //** breaks test 1. //< 0.5, 0.5,-0.5, 0.5 >; //** breaks test 1. //< 0.5, 0.5, 0.5,0.5>;// ** double pass llEuler2Rot(<00,00,90>*DEG_TO_RAD); llOwnerSay("---test 1---"); llOwnerSay((string)rot); llOwnerSay((string)(uRot2Euler(rot)*RAD_TO_DEG)); llOwnerSay((string)llEuler2Rot(uRot2Euler(rot))); llOwnerSay("---test 2---"); llOwnerSay((string)rot); llOwnerSay((string)uRot2Euler2(rot)); llOwnerSay((string)uEuler2Rot2(uRot2Euler2(rot))); /*llOwnerSay("---test 3---"); vector vec = <15,-45,35>*DEG_TO_RAD; llOwnerSay((string)uEuler2Rot(vec)); llOwnerSay((string)llEuler2Rot(vec));*/ } } Does anyone perhaps have a working version of uRot2Euler() in their back pocket? Edited May 6, 2021 by Quistessa 1 Link to comment Share on other sites More sharing options...
Mollymews Posted May 6, 2021 Share Posted May 6, 2021 the issue with this is that a lot of eulers translate to the same quaternion. example: <-180,-180,-180> <-180,-180, 180> <-180, 180,-180> < 180,-180,-180> < 180,-180, 180> < 180, 180,-180> < 180, 180, 180> < 0, 0, 0> all translate to <0, 0, 0, 1> < 180, 180, 0> < 0, 0, 180> both translate to <0, 0, 1, 0> < 0, 180, 180> < 180, 0, 0> to <1, 0, 0, 0> and when we go to convert it back, we have no way of knowing what the degrees typed in were a thing is that eulers derived from quaternions are not quite the same thing as degrees. A way to help to get our heads round this is with a degrees to euler translator. Example: vector Deg2Euler(vector degrees) { return llRot2Euler(llEuler2Rot(degrees * DEG_TO_RAD)) * RAD_TO_DEG; } // examples: vector degrees = <180, 180, 180>; vector euler = Deg2Euler(degrees); // euler = <0,0,0> degrees = <180, -180, 180>; euler = Deg2Euler(degrees); // euler = <0,0,0> then after a time we learn to do this translation in our heads 1 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 6, 2021 Author Share Posted May 6, 2021 14 minutes ago, Mollymews said: the issue with this is that a lot of eulers translate to the same quaternion. I'm aware of that, it makes the problem hard, but it shouldn't make it intractable. I'm just looking for a function such that llEuler2Rot(uRot2Euler(rot)) is roughly the same as rot. (floating point errors and normalization make exact matching unlikely) There are multiple eulers for a given orientation, but only two distinct quaternions. 1 Link to comment Share on other sites More sharing options...
Mollymews Posted May 6, 2021 Share Posted May 6, 2021 28 minutes ago, Quistessa said: I'm aware of that, it makes the problem hard, but it shouldn't make it intractable we have to create a translation matrix to solve this for all quaternions 1 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 6, 2021 Author Share Posted May 6, 2021 (edited) translation shlanslation: default { touch_start(integer total_number) { rotation rot_zero = llGetRot(); rotation rot = llGetRot(); vector ret; vector temp; temp = <0,0,1>*rot; ret.x = llAtan2(temp.z,temp.y)-PI/2; rot = rot/llEuler2Rot(<ret.x,0,0>); llSetRot(rot); llSetText((string)(ret*RAD_TO_DEG),<0,1,1>,1.0); llSleep(1.0); temp = <0,0,1>*rot; ret.y = -llAtan2(temp.z,temp.x)+PI/2; rot = rot/llEuler2Rot(<0,ret.y,0>); llSetRot(rot); llSetText((string)(ret*RAD_TO_DEG),<1,0,1>,1.0); llSleep(1.0); temp = <1,0,0>*rot; ret.z = llAtan2(temp.y,temp.x); rot = rot/llEuler2Rot(<0,0,ret.z>); llSetRot(rot); llSetText((string)(ret*RAD_TO_DEG),<1,1,0>,1.0); llSleep(3.0); llSetRot(rot_zero); } } Nothing a little stepping through 1 step at a time can't solve. Put that in a prim and watch it magically work out its rotation. Sure slightly weird things will happen if it's /exactly/ on an edge case *cough*<0,90,0>*cough* but it's good enough for what I'm going to use it for. . . Edited May 6, 2021 by Quistessa 1 Link to comment Share on other sites More sharing options...
Mollymews Posted May 6, 2021 Share Posted May 6, 2021 1 hour ago, Quistessa said: translation shlanslation: your code can work for a subset of the matrix yes it won't work with all elements of the matrix example using your code: rotation rot = <1,0,0,0>; // rotated on the x axis by PI (3.14..) radians vector ret; vector temp; temp = <0,0,1>*rot; ret.x = llAtan2(temp.z,temp.y)-PI/2; rot = rot/llEuler2Rot(<ret.x,0,0>); // llSetRot(rot); // llSetText((string)(ret*RAD_TO_DEG),<0,1,1>,1.0); // llSleep(1.0); temp = <0,0,1>*rot; ret.y = -llAtan2(temp.z,temp.x)+PI/2; rot = rot/llEuler2Rot(<0,ret.y,0>); // llSetRot(rot); ... at this point rot = <0,0,0,1> ... 1 Link to comment Share on other sites More sharing options...
Profaitchikenz Haiku Posted May 6, 2021 Share Posted May 6, 2021 (edited) 7 hours ago, Quistessa said: Inspired by a recent thread I must have missed that. As a result of my recent experiences I've realised that there is a definite need for something like this. For many of us the wiki page and various examples lead us down a monkey-see-monkey-do path: we use the functions with an imperfect understanding of what is actually happening. For most of the time that's fine. Every now and then somebody turns up who isn't happy with the "stop asking why, just do the calculations" dogma, rather like Quantum Physics with the Copenhagen Interpretation approach, "never mind that it doesn't make sense, just get on with using the formulae, they're proven". Such people need to understand in their heads what is going on when they use rotations. I've got a few insights into Quarternions as a result of trying to find a way to demonstrate to somebody that vector*rotation is doing for you what calculating three separate triangles does. I can explain it quite well by showing that the x,y,z components of a rotation equate pretty well to multiples of PI, but the fourth component, explained elsewhere as the total amount of rotation on the object, I have no real explanation for at the moment. As Molly has shown and as I have scratched my head over, various combinations of X,Y,Z come back to the same value in the W component. I am especially intrigued by the state so far, in that it seems impossible to precisely reverse rotations to Eulers in all cases? Edited May 6, 2021 by Profaitchikenz Haiku 1 Link to comment Share on other sites More sharing options...
Mollymews Posted May 6, 2021 Share Posted May 6, 2021 to help with understanding the relationship between quaternions and eulers I paste here a SL port of the standard algorithms to convert between the two the standard algorithms are explained here: https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles i haven't tested it much, but am reasonably sure llEuler2Rot and llRot2Euler use the same algorithms rotation Vec2Quat(vector v) { float cz = llCos(v.z * 0.5); float sz = llSin(v.z * 0.5); float cy = llCos(v.y * 0.5); float sy = llSin(v.y * 0.5); float cx = llCos(v.x * 0.5); float sx = llSin(v.x * 0.5); rotation q; q.x = sx * cy * cz - cx * sy * sz; q.y = cx * sy * cz + sx * cy * sz; q.z = cx * cy * sz - sx * sy * cz; q.s = cx * cy * cz + sx * sy * sz; return q; } vector Quat2Vec(rotation q) { vector v; // x-axis float s = 2 * (q.s * q.x + q.y * q.z); float c = 1 - 2 * (q.x * q.x + q.y * q.y); v.x = llAtan2(s, c); // y-axis s = 2 * (q.s * q.y - q.z * q.x); if (llFabs(s) >= 1) v.y = (-1 | (s < 0)) * PI_BY_TWO; else v.y = llAsin(s); // z-axis s = 2 * (q.s * q.z + q.x * q.y); c = 1 - 2 * (q.y * q.y + q.z * q.z); v.z = llAtan2(s, c); return v; } default { state_entry() { vector v = <PI_BY_TWO, 0, 0>; rotation q = Vec2Quat(v); vector w = Quat2Vec(q); vector u = llRot2Euler(q); llOwnerSay(llDumpList2String([v, q, w, u], " ")); } } 2 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 6, 2021 Author Share Posted May 6, 2021 3 hours ago, Mollymews said: llRot2Euler /me facepalms. I swear I looked at the wiki 4 or five times searching for that function, only to glaze over and not see it. . . 1 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 6, 2021 Author Share Posted May 6, 2021 (edited) 7 hours ago, Mollymews said: your code can work for a subset of the matrix yes it won't work with all elements of the matrix example using your code: Quote ... at this point rot = <0,0,0,1> ... That's the point. for all inputs of rot, after my algorithm rot is rotated to <0,0,0,1> and ret is mapped to the euler vector for the original rotation. Your algorithm seems to use the standard pitch-yaw-roll convention. SL doesn't. vector uRot2Euler(rotation rot) { vector ret; vector temp; //x-axis temp = <0,0,1>*rot; ret.x = llAtan2(temp.z,temp.y)-PI/2; rot = rot/llEuler2Rot(<ret.x,0,0>); //y-axis temp = <0,0,1>*rot; ret.y = -llAtan2(temp.z,temp.x)+PI/2; rot = rot/llEuler2Rot(<0,ret.y,0>); //z-axis temp = <1,0,0>*rot; ret.z = llAtan2(temp.y,temp.x); return ret; } vector uRot2Euler2(rotation rot) { vector ret; vector temp = <1,0,0>*rot; ret.z = llAtan2(temp.y,temp.x); rot = rot/llEuler2Rot(<0,0,ret.z>); temp = <1,0,0>*rot; ret.y = -llAtan2(temp.z,temp.x); rot = rot/llEuler2Rot(<0,ret.y,0>); temp = <0,1,0>*rot; ret.x = llAtan2(temp.z,temp.y); return ret; } vector Quat2Vec(rotation q) { vector v; // x-axis float s = 2 * (q.s * q.x + q.y * q.z); float c = 1 - 2 * (q.x * q.x + q.y * q.y); v.x = llAtan2(s, c); // y-axis s = 2 * (q.s * q.y - q.z * q.x); if (llFabs(s) >= 1) v.y = (-1 | (s < 0)) * PI_BY_TWO; else v.y = llAsin(s); // z-axis s = 2 * (q.s * q.z + q.x * q.y); c = 1 - 2 * (q.y * q.y + q.z * q.z); v.z = llAtan2(s, c); return v; } default { touch_start(integer total_number) { //rotation q = <0.5,-0.5,-0.5,0.5>; //rotation q = <1,0,0,0>; //rotation q = <0.5,-0.5,0.5,0.5>; //rotation q = llGetRot(); rotation q =<0.306186, 0.176777, 0.306186, 0.883884>; vector a = llRot2Euler(q)*RAD_TO_DEG; vector b = uRot2Euler(q)*RAD_TO_DEG; vector c = Quat2Vec(q)*RAD_TO_DEG; vector d = uRot2Euler2(q)*RAD_TO_DEG; string text = llDumpList2String([q,a,b,c,d],"\n"); //llOwnerSay(text); llSetText(text,<0,1,0>,1.0); } } output: <30.000000, 30.000000, 30.000000> (llRot2Euler) <30.000000, 30.000000, 30.000000> (uRot2Euler) (my implementation) <40.893390, 7.180757, 40.893390> (molly's implementation) <40.893390, 7.180757, 40.893390> (my first working implementation) Edited May 6, 2021 by Quistessa added output. 1 Link to comment Share on other sites More sharing options...
Mollymews Posted May 7, 2021 Share Posted May 7, 2021 9 hours ago, Quistessa said: <30.000000, 30.000000, 30.000000> (llRot2Euler) <30.000000, 30.000000, 30.000000> (uRot2Euler) (my implementation) <40.893390, 7.180757, 40.893390> (molly's implementation) when we mix up our matrixes then we get different results as you show when we use the method that applies to the matrix we are using then vector v = <30.0, 30.0, 30.0>; rotation a = llEuler2Rot(v * DEG_TO_RAD); vector b = llRot2Euler(a) * RAD_TO_DEG; rotation c = Vec2Quat(v * DEG_TO_RAD); vector d = Quat2Vec(c) * RAD_TO_DEG; rotation e = llEuler2Rot(v * DEG_TO_RAD); vector f = uRot2Euler(e) * RAD_TO_DEG; llOwnerSay(llDumpList2String(["LSL", v, a, b], " ")); llOwnerSay(llDumpList2String(["Mol", v, c, d], " ")); llOwnerSay(llDumpList2String(["Qui", v, e, f], " ")); // output is // LSL <30.000000, 30.000000, 30.000000> <0.306186, 0.176777, 0.306186, 0.883884> <30.000000, 30.000000, 30.000000> // Mol <30.000000, 30.000000, 30.000000> <0.176777, 0.306186, 0.176777, 0.918559> <30.000000, 30.000000, 30.000000> // Qui <30.000000, 30.000000, 30.000000> <0.306186, 0.176777, 0.306186, 0.883884> <30.000000, 30.000010, 30.000000> 2 Link to comment Share on other sites More sharing options...
Mollymews Posted May 7, 2021 Share Posted May 7, 2021 i looked up in the SL viewer source code for how Linden does this: rot to euler (file: llquaternion) // SJB: This code is correct for a logicly stored (non-transposed) matrix; // Our matrices are stored transposed, OpenGL style, so this generates the // INVERSE matrix, or the CORRECT matrix form an INVERSE quaternion. // Because we use similar logic in LLMatrix3::quaternion(), // we are internally consistant so everything works OK :) LLMatrix3 LLQuaternion::getMatrix3(void) const { LLMatrix3 mat; F32 xx, xy, xz, xw, yy, yz, yw, zz, zw; xx = mQ[VX] * mQ[VX]; xy = mQ[VX] * mQ[VY]; xz = mQ[VX] * mQ[VZ]; xw = mQ[VX] * mQ[VW]; yy = mQ[VY] * mQ[VY]; yz = mQ[VY] * mQ[VZ]; yw = mQ[VY] * mQ[VW]; zz = mQ[VZ] * mQ[VZ]; zw = mQ[VZ] * mQ[VW]; mat.mMatrix[0][0] = 1.f - 2.f * ( yy + zz ); mat.mMatrix[0][1] = 2.f * ( xy + zw ); mat.mMatrix[0][2] = 2.f * ( xz - yw ); mat.mMatrix[1][0] = 2.f * ( xy - zw ); mat.mMatrix[1][1] = 1.f - 2.f * ( xx + zz ); mat.mMatrix[1][2] = 2.f * ( yz + xw ); mat.mMatrix[2][0] = 2.f * ( xz + yw ); mat.mMatrix[2][1] = 2.f * ( yz - xw ); mat.mMatrix[2][2] = 1.f - 2.f * ( xx + yy ); return mat; } euler to rot (file: m3math) // SJB: This code is correct for a logicly stored (non-transposed) matrix; // Our matrices are stored transposed, OpenGL style, so this generates the // INVERSE quaternion (-x, -y, -z, w)! // Because we use similar logic in LLQuaternion::getMatrix3, // we are internally consistant so everything works OK :) LLQuaternion LLMatrix3::quaternion() const { LLQuaternion quat; F32 tr, s, q[4]; U32 i, j, k; U32 nxt[3] = {1, 2, 0}; tr = mMatrix[0][0] + mMatrix[1][1] + mMatrix[2][2]; // check the diagonal if (tr > 0.f) { s = (F32)sqrt (tr + 1.f); quat.mQ[VS] = s / 2.f; s = 0.5f / s; quat.mQ[VX] = (mMatrix[1][2] - mMatrix[2][1]) * s; quat.mQ[VY] = (mMatrix[2][0] - mMatrix[0][2]) * s; quat.mQ[VZ] = (mMatrix[0][1] - mMatrix[1][0]) * s; } else { // diagonal is negative i = 0; if (mMatrix[1][1] > mMatrix[0][0]) i = 1; if (mMatrix[2][2] > mMatrix[i][i]) i = 2; j = nxt[i]; k = nxt[j]; s = (F32)sqrt((mMatrix[i][i] - (mMatrix[j][j] + mMatrix[k][k])) + 1.f); q[i] = s * 0.5f; if (s != 0.f) s = 0.5f / s; q[3] = (mMatrix[j][k] - mMatrix[k][j]) * s; q[j] = (mMatrix[i][j] + mMatrix[j][i]) * s; q[k] = (mMatrix[i][k] + mMatrix[k][i]) * s; quat.setQuat(q); } return quat; } would be possible to port this code to LSL. Be a little bit messy tho 1 Link to comment Share on other sites More sharing options...
Innula Zenovka Posted May 7, 2021 Share Posted May 7, 2021 Sorry, I don't understand, but what's the difference between these userfunctions and llRot2Euler? 1 Link to comment Share on other sites More sharing options...
Wulfie Reanimator Posted May 7, 2021 Share Posted May 7, 2021 Just now, Innula Zenovka said: Sorry, I don't understand, but what's the difference between these userfunctions and llRot2Euler? I was gonna ask that when the thread was first posted, but I assumed the whole point was to see how the calculations are done. But it also seems like the existence of that function was mistaken. 1 1 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 7, 2021 Author Share Posted May 7, 2021 (edited) 37 minutes ago, Innula Zenovka said: Sorry, I don't understand, but what's the difference between these userfunctions and llRot2Euler? SOme of them are identical to the ll functions, some use a different (more intuitive IMO) convention: group 1: llRot2Euler() uRot2Euler() (not the version in my first post, read down) are the inverse of llEuler2Rot() should be roughly identical to the linden-versions. group 2 : Vec2Quat() uRot2Euler2() are the inverse of Quat2Vec() Do the same things but the actual output is different. if you (for some reason) find some pitch-yaw-roll angles 'in the wild' they will more probably work with group 2 than group 1. according to Wikipedia there are no less than 12 different conventions for "euler angles". https://en.wikipedia.org/wiki/Euler_angles Edit: fun factoid: 'Group 2' are called 'nautical angles' or 'Cardan angles' . Group one doesn't have a fancy name, therefore group 2 beats group 1 :P. also, part of the point of the thread as wolfie says, I didn't realize llRot2Euler existed. Edited May 7, 2021 by Quistessa I read Wikipedia. 1 1 Link to comment Share on other sites More sharing options...
Profaitchikenz Haiku Posted May 7, 2021 Share Posted May 7, 2021 I'm slightly worried by something Molly has been illustrating, it suggests there is non-commutability between Eulers and quaternions (3-to-1 mapping between some eulers and a single rotation for example). However, these only seem to be occurring at distinct positions such as right-angles and reversals? As far as I can see, the main point of having the two conversions is that very very few of us can visualise rotations as quarternions, and very few can even visualise things as radians when we're building. If i were the norm, or even achievable with a little education, the build floater for orientation could be in radians or even quarternions instead of degrees. 1 Link to comment Share on other sites More sharing options...
Mollymews Posted May 7, 2021 Share Posted May 7, 2021 (edited) 6 hours ago, Wulfie Reanimator said: I was gonna ask that when the thread was first posted, but I assumed the whole point was to see how the calculations are done. the conversation goes back to a question previous asked. Which was how to be able to use vector degree so that Rot2Deg(llGetRot()) will return the same degree value that was input to begin to do this then we have to understand how quaternions and vectors are translated/converted, so the code examples posted so begin... we input llSetRot(<0, 91, 0> * DEG_TO_RAD) and llRot2Euler(llGetRot()) * RAD_TO_DEG returns <180, 89, 180> and we think we will write our function Rot2Deg() which returns <0,91,0>, the same vector degree we input but.. suppose we input <180, 89, 180> then our Rot2Deg is going to return <0, 91, 0> and not what we entered <180, 89, 180> this is the issue with trying to do this, we have to code up a translation matrix for all degree values that we want to work with. and then restrict the input to that matrix. I.e if we try to input <180, 89, 180> then the matrix raises an error: Value out of bounds or.. we can go: hmm! that's going to be a pain. Maybe I spend a bit of time looking at the values returned by llRot2Euler() * RAD_TO_DEG, and I will learn what they are and just input them. So that same in is same out i just add on here. I had a bit more dig into llQuaternion and ported the following code, which should output the same as llEuler2Rot(). Which can be used as the companion to Quitessa's uRot2Euler, for exploring these things further. In a close enough is good enough way rotation uEuler2Rot(vector v) { v.x *= 0.5; v.y *= 0.5; v.z *= 0.5; float sinX = llSin(v.x); float cosX = llCos(v.x); float sinY = llSin(v.y); float cosY = llCos(v.y); float sinZ = llSin(v.z); float cosZ = llCos(v.z); rotation r; r.x = sinX * cosY * cosZ + cosX * sinY * sinZ; r.y = cosX * sinY * cosZ - sinX * cosY * sinZ; r.z = cosX * cosY * sinZ + sinX * sinY * cosZ; r.s = cosX * cosY * cosZ - sinX * sinY * sinZ; return r; } Edited May 7, 2021 by Mollymews + 2 1 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 7, 2021 Author Share Posted May 7, 2021 (edited) For didactic purposes, I'll provide a mathematically equivalent version of uEuler2Rot: rotation uEuler2Rot(vector v) // equivalent to llEuler2Rot { return //XYZ intrinsic = ZYX extrinsic <0,0,llSin(v.z/2),llCos(v.z/2)> * <0,llSin(v.y/2),0,llCos(v.y/2)> * <llSin(v.x/2),0,0,llCos(v.x/2)> ; } rotation uEuler2Rot2(vector v) //Yaw Pitch Roll { return //ZYX intrinsic = XYZ extrinsic <llSin(v.x/2),0,0,llCos(v.x/2)> * <0,llSin(v.y/2),0,llCos(v.y/2)> * <0,0,llSin(v.z/2),llCos(v.z/2)> ; } It's just doing global rotations on the 3 axes. one nice way of understanding what a Quaternion is is to convert it to 'axis-angle' form: list uRot2AxisAngle(rotation q) { if(q.s==1.0) return [<1,0,0>,0.0]; float s = llSin(llAcos(q.s)); llOwnerSay((string)s+" "+(string)q.s); // debug return [<q.x/s,q.y/s,q.z/s>,llAcos(q.s)*2]; } rotation uAxisAngle2Rot(vector v, float th) { float s = llSin(th/2); return <s*v.x,s*v.y,s*v.z,llCos(th/2)>; } default { touch_start(integer total_number) { list aa = uRot2AxisAngle(llGetRot()); llSetText( (string)(llList2Vector(aa,0))+ (string)(llList2Float (aa,1)*RAD_TO_DEG), <0,1,0>,1.0); } } something to try yourself IRL: take a roughly-spherical (as you get better at this you can try with less and less spherical) small object and place it between your thumb and forefinger, then rotate the object. if the line between your fingers passes through the center of the object, the axis between forefinger and thumb is the axis of rotation. Beieve it or not you can move the object to any orientation with only one of these operations. (assuming you can actually hold on to the object on any axis). Edited May 7, 2021 by Quistessa 2 Link to comment Share on other sites More sharing options...
Mollymews Posted May 7, 2021 Share Posted May 7, 2021 7 hours ago, Quistessa said: For didactic purposes, I'll provide a mathematically equivalent version of uEuler2Rot: for a tiny speed increase multiply by 0.5 rather than divide by 2 example: llCos(v.z * 0.5) other than that I like it 2 Link to comment Share on other sites More sharing options...
Mollymews Posted May 8, 2021 Share Posted May 8, 2021 just a fyi looking into llquaternion it turns out that Linden use the inverse of the standard algorithm. The comments say that this better suits Linden for OpenGL purposes. Something I didn't know til I had a look this is the standard: r.x = sinX * cosY * cosZ - cosX * sinY * sinZ; r.y = cosX * sinY * cosZ + sinX * cosY * sinZ; r.z = cosX * cosY * sinZ - sinX * sinY * cosZ; r.s = cosX * cosY * cosZ + sinX * sinY * sinZ; and this is the Linden inverse r.x = sinX * cosY * cosZ + cosX * sinY * sinZ; r.y = cosX * sinY * cosZ - sinX * cosY * sinZ; r.z = cosX * cosY * sinZ + sinX * sinY * cosZ; r.s = cosX * cosY * cosZ - sinX * sinY * sinZ; note that + and - are inverted 2 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 8, 2021 Author Share Posted May 8, 2021 25 minutes ago, Mollymews said: looking into llquaternion it turns out that Linden use the inverse of the standard algorithm. (I believe I already mentioned that. . .) 26 minutes ago, Mollymews said: The comments say that this better suits Linden for OpenGL purposes. Something I didn't know til I had a look Huh, nice to know they have some reason for making it that way. A shame that it makes it harder to use for anything other than single-axis rotations. 2 Link to comment Share on other sites More sharing options...
Wulfie Reanimator Posted May 10, 2021 Share Posted May 10, 2021 (edited) 1 hour ago, Mollymews said: i think it would be quite good if somebody with wiki edit capabilities could add Quistessa's uEuler2Rot and uRot2Euler to the respective llEuler2Rot and llRot2Euler pages, as LSL coded examples of how what these functions do. Similar to the LSL coded examples of other rotational functions on the wiki edit add. looks like somebody already did for llEuler2Rot. If they could do the same for llRot2Euler that would be good I can do that if @Quistessa wants it. Which version of the function(s) would I add exactly? Also, does it matter that uRot2Euler seems to be going in XYZ order despite the wiki page saying "The Euler angle vector (in radians) is converted from a rotation by doing the rotations around the 3 axes in Z, Y, X order."? Could it be edited for clarity? Edited May 10, 2021 by Wulfie Reanimator Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 10, 2021 Author Share Posted May 10, 2021 39 minutes ago, Wulfie Reanimator said: Also, does it matter that uRot2Euler seems to be going in XYZ order despite the wiki page saying "The Euler angle vector (in radians) is converted from a rotation by doing the rotations around the 3 axes in Z, Y, X order."? Could it be edited for clarity? XYZ order rotations about global/extrinsic axes are the same as ZYX order rotations about local/intrinsic axes. so it is technically correct to say that llEuler2Rot rotates the object about the X, then Y, then Z, axes of the object. IMO it would be better if it said that llEuler2Rot rotates in ZYX order about the global axes. Quote The Euler angle vector (in radians) is converted to a rotation by doing the rotations around the 3 axes in Z, Y, X order. So llEuler2Rot(<1.0, 2.0, 3.0> * DEG_TO_RAD) generates a rotation by first rotating 3 degrees around the global Z axis, then rotating the result around the global Y axis, and finally rotating that 1 degree around the global X axis. Seems to be what the wiki says now and that's accurate. Might be nice to add something to the wiki saying Euler2rot/Rot2Euler are /not/ nautical angles/roll-pitch-yaw. p.s. feel free to add/not add any of my examples or derivatives thereof to the wiki. Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 10, 2021 Author Share Posted May 10, 2021 (edited) I just realized that LL also implements quaternions themselves opposite form standard. i*j=-k . (or equivalently you could imagine that ll flips every quaternion expression before evaluating it. . .) llOwnerSay((string)(<1,0,0,0>*<0,1,0,0>)); // output <0,0,-1,0> // would be <0,0,1,0> by standard non-SL convention. Would explain why @Wulfie Reanimator was a tad confused by the implementation. Personally, I think LL convention is more sensible for practical use. a*b*c is 'a then b then c' global rotations. c(b(a(x))) or c∘b∘a∘x is nice for pure math though. Edited May 10, 2021 by Quistessa clarifying comment. Link to comment Share on other sites More sharing options...
Wulfie Reanimator Posted May 10, 2021 Share Posted May 10, 2021 33 minutes ago, Quistessa said: Would explain why @Wulfie Reanimator was a tad confused by the implementation. I'm just speaking in general, I haven't looked at the implementation too closely since doing the calculations by hand (never mind dealing with matrices) makes my head spin real fast. I've gone ahead and added your uRot2Euler to the wiki page, though. Link to comment Share on other sites More sharing options...
Recommended Posts
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