Wulfie Reanimator Posted May 24, 2021 Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Wulfie Reanimator Link to comment Share on other sites More sharing options...
Profaitchikenz Haiku Posted May 24, 2021 Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Profaitchikenz Haiku Link to comment Share on other sites More sharing options...
Wulfie Reanimator Posted May 24, 2021 Author Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Wulfie Reanimator Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 24, 2021 Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Quistessa Link to comment Share on other sites More sharing options...
Profaitchikenz Haiku Posted May 24, 2021 Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Profaitchikenz Haiku 1 Link to comment Share on other sites More sharing options...
Wulfie Reanimator Posted May 24, 2021 Author Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Wulfie Reanimator Link to comment Share on other sites More sharing options...
Qie Niangao Posted May 24, 2021 Share Posted May 24, 2021 56 minutes ago, Wulfie Reanimator said: Also, how exactly is PRIM_ROTATION broken? I haven't noticed any issues with it. I assume this comment is about the notorious SVC-93 (which affects only child prims). 1 1 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 24, 2021 Share Posted May 24, 2021 14 minutes ago, Qie Niangao said: I assume this comment is about the notorious SVC-93 (which affects only child prims). I ended up rediscovering that exact phenomenon, good to know it has a JIRA that explains it very well. Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 24, 2021 Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Quistessa Link to comment Share on other sites More sharing options...
Wulfie Reanimator Posted May 24, 2021 Author Share Posted May 24, 2021 (edited) 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 May 24, 2021 by Wulfie Reanimator Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 24, 2021 Share Posted May 24, 2021 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>)) 1 Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 25, 2021 Share Posted May 25, 2021 (edited) 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 May 25, 2021 by Quistessa used yaw in roll calculation since we already computed it. 1 Link to comment Share on other sites More sharing options...
Wulfie Reanimator Posted May 25, 2021 Author Share Posted May 25, 2021 (edited) 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 May 25, 2021 by Wulfie Reanimator Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 25, 2021 Share Posted May 25, 2021 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 More sharing options...
Wulfie Reanimator Posted May 25, 2021 Author Share Posted May 25, 2021 (edited) 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. 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: 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 May 25, 2021 by Wulfie Reanimator Link to comment Share on other sites More sharing options...
Quistess Alpha Posted May 25, 2021 Share Posted May 25, 2021 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)); } 1 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