Jump to content

fire objects at equal intervals around center point


Judy Hynes
 Share

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

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

Recommended Posts

I'm making a script that fires objects off around a circular pattern.  It's basically a firework launcher that shoots the fireworks out as if the launcher is rotating a fixed amount between each shot until it rotates all the way back to where it started.  I have it nearly doing what I want, but not quite.

integer NUM_OF_SHOTS = 10;
integer SPEED = 30;
float SHOT_ANGLE_UP = 80.0;
string OBJECT_TO_REZ = "firework";

default
{
    touch_start(integer num_detected)
    {
        rotation up_tilt_rot = llEuler2Rot(<0, SHOT_ANGLE_UP, 0>*DEG_TO_RAD);
        float bearing_turn_per_shot = 2*PI/NUM_OF_SHOTS;
        integer i=0;
        for(; i<NUM_OF_SHOTS; ++i)
        {
            rotation bearing_rot = llEuler2Rot(<0, 0, bearing_turn_per_shot*i>);
            rotation final_rot = up_tilt_rot * bearing_rot;
            llRezObject(OBJECT_TO_REZ, llGetPos()+<0, 0, 2>, llRot2Fwd(final_rot)*SPEED, final_rot, 0);
            llSleep(0.5);
        }
    }
}

The idea is that it will rotate 2PI / NUM_OF_SHOTS each time, which means it should make its way around the circle by the time it's done.  From what I've read on rotations, I can compose them together to make a final rotation, so that's what I did.  I first did the up_tilt_rot to set the angle upwards that it will launch (this is the same for every shot).  Then each shot I calculate how far around the circle it should rotate, which is the bearing_rot you see in the code.  Then I compose them together, up tilt first, to get the final rotation for the shot.  I use that rotation on the firework and apply velocity rotated to its forward direction so the firework flies in its local x direction at the defined speed.

All this seems to work great, except I don't get the full 360 degree circle.  Instead, I get a semi-circle in the positive x direction for the first half of the shots, then the second half retrace that same semi-circle again.  I don't get any along the negative x half of the circle.

I thought that my bearing_rot would just keep incrementing all the way around the circle, but I guess that's not true.  It confuses me that it seem to "reset" on shot 6 to return to where shot 1 was launched.  Can anyone help me understand what I'm doing wrong?

Link to comment
Share on other sites

3 hours ago, Judy Hynes said:

Can anyone help me understand what I'm doing wrong?

You're pointing your rockets 80 degrees into the ground rather than 80 degrees into the air. a (positive) rotaiton about the y axis will "nose down". if you change the 80 to -80 and add a llSetRot(final_rot); at the end of the for loop before the sleep (for visual clarity) it seems to do exactly what you expect. (I tested it with small temporary physical spheres instead of rockets, speed of 2 and angles of -80 and then -25)

Here's the lengthy response I wrote before actually testing the thing. . . with script fixed with some testing afterwords. . .

 

So first off terminology. The "bearing" I think If I'm reading right is what I would call "Yaw" and "Up tilt" what I would call "Pitch". Nothing wrong with using whatever words oyu're comfortable with, just if I try and match my vocabulary I'm going to get confused myself. https://simple.wikipedia.org/wiki/Pitch,_yaw,_and_roll (Pitch yaw and roll are related but also completely different from Euler angles as implemented in LSL. I have a few functions in the lsl library for converting rotations to YPR format and back.)

Second. "Rotations" (actually quaternions) use the same mathematics to represent two related but semantically different concepts. They can represent the Orientation of an object, that is, is the object facing north, south, up, diagonal etc. and they can also represent changes in orientation so, rotate 30 degrees to the west, rotate from the y axis to the z axis etc.

Third. a change can be applied to an orientation in two different ways: (in LSL) to apply an Extrinsic change (c) to an orientation (or) you multiply on the right: or*c ; Extrinsic means with respect to the global axes ; if you apply an extrinsic rotation about the z axis to an orientation, you will change the yaw of of that orientation, regardless of its current pitch or roll. To apply an Intrinsic change (c) to an orientation (or) you multiply on the left, c*or ; Intrinsic means with respect to the current axes of the orientation (like you would see if you edit the position of an object with snap-local enabled) ; if an orientation has no roll, then applying an intrinsic rotation about the y axis will change the orientation's pitch.

Actually rereading your example, that's a bit more than you need, but it's good to understand what you're able to do. Your issue seems to actually be that you're not using a variable defined outside the loop, but creating a new one each time.

integer NUM_OF_SHOTS = 10;
integer SPEED = 2;
float SHOT_ANGLE_UP = -25.0;
string OBJECT_TO_REZ = "Bullet";

default
{
    touch_start(integer num_detected)
    {
        rotation delta_yaw_per_shot = llEuler2Rot(<0,0,2*PI/NUM_OF_SHOTS>);
        rotation orientation = llEuler2Rot(<0, SHOT_ANGLE_UP, 0>*DEG_TO_RAD);
        integer i=0;
        for(; i<NUM_OF_SHOTS; ++i)
        {
            llSetRot(orientation); // for illustration.
            llRezObject(OBJECT_TO_REZ, llGetPos()+<0,0,2>, llRot2Fwd(orientation)*SPEED, orientation, 0);
            orientation*=delta_yaw_per_shot;
            
            llSleep(0.5);
        }
        llSetRot(orientation); // for illustration.
    }
}

 

Link to comment
Share on other sites

Ah ok I get it now.  The "shooting down" thing explains why I saw the objects scatter a lot more than I expected; they were being slammed into the ground.

I see that you took the tact of remembering the orientation each time and applying the delta_yaw_per_shot each time, incrementing around the circle.  That makes sense to me.  I think what I was doing was also correct though, creating the "bearing_rot" each time with increasingly larger numbers each time.....0x then 1x then 2x and so on.  Is there something fundamentally flawed about that technique or just another way to think about the same thing?

 

Link to comment
Share on other sites

28 minutes ago, Judy Hynes said:

Is there something fundamentally flawed about that technique or just another way to think about the same thing?

Yeah, there's nothing fundamentally wrong with it, and for very small changes (say 1 degree or smaller) your method would probably do better at avoiding floating point rounding oddities.

one thing I would recommend though is perhaps familiarizing yourself with llAxisAngle2Rot() as an alternative to llEuler2Rot. Eulers are fine for single axis things, but tl;dr they're bad mojo if you want/need to do anything else with them.

another nitpick, instead of 2*PI and PI/2 remember to use the constants TWO_PI and PI_BY_TWO if you can.

Link to comment
Share on other sites

7 hours ago, Quistess Alpha said:

one thing I would recommend though is perhaps familiarizing yourself with llAxisAngle2Rot() as an alternative to llEuler2Rot. Eulers are fine for single axis things, but tl;dr they're bad mojo if you want/need to do anything else with them.

Yeah, as I understand it, llAxisAngle2Rot lets you define an arbitrary axis to rotate around and not be constrained to the fixed Euler axes.  For my purposes, it comes out the same though right?  I just wanted to rotate about one axis and it happened to be one of the Euler ones.

rotation delta_yaw_per_shot = llEuler2Rot(<0.0, 0.0, TWO_PI/shot_count>);
rotation same_as_above = llAxisAngle2Rot(<0.0, 0.0, 1.0>, TWO_PI/shot_count);
  

I think in the above both come out to the same rotation.

I get why defining my own axis would be very handy, like if I wanted to rotate 90 degrees about an axis that's a diagonal in the x-z plane I could do llAxisAngle2Rot(<1.0, 0.0, 1.0>, PI_BY_TWO);  What I struggle to figure out is how I could define a similarly-useful custom axis for my specific case that would be any better than the Euler function it uses now.  Is there some axis I could have defined that would have gotten me both the pitch and yaw I needed in one step instead of two?  I don't know how I'd go about figuring that out.  (I suspect it's not possible since I wanted to rotate about two axes that are orthogonal, but I don't have a strong enough background in this kind of thing to be sure.)

 

Edited by Judy Hynes
Link to comment
Share on other sites

llEuler2Rot can be a problematic function. If you look at the description in the LSL wiki, you'll find that

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.

That somewhat convoluted process can result in gimbal lock under some conditions.  Even when it doesn't, it means that you are calculating a rotation in multiple steps (and possibly introducing small errors) instead of simply specifying the rotation directly as a quaternion.  (That's especially easy for rotation axes like <1,0,0>, <0,1,0> and <0,0,1>.  That would simplify your case quite a bit.)  When you do actually need to calculate a rotation (in the general case when you aren't rotating around one of the primary axes), it's almost as easy and less problematic to define the rotation axis ( a vector) and then use trigonometry to generate the rotation.  The function llAxisAngle2Rot is defined by

rotation llAxisAngle2Rot( vector axis, float angle )
{
    axis = llVecNorm( axis ) * llSin( angle/2 );
    return <axis.x, axis.y, axis.z, llCos( angle/2 )>;
}

Tessa will probably have a better way to think about this, but this is what has always made sense to me.

  • Like 1
Link to comment
Share on other sites

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