Thursday, July 31, 2008

Two Week Break - Africa

I will not be posting for two weeks because I'm co-leading a missions/community development trip to South Africa. This is my third trip to Africa and my second as a leader. I originally wanted to make a difference in the HIV/AIDS pandemic and joined a trip sponsored by my church. Over the past few years it has become apparent that HIV/AIDS is really just a side-effect of extreme poverty. Last summer we searched for a new organization to work with and landed with Bridges Of Hope outside Cape Town. This will be our first team with this new partner ship.

We've since adopted Sweet Home, one of the poorest places I have ever been and a place with zero opportunity for residents. Bridges Of Hope has been working for the past five years in a neighboring community called Phillipe and the changes are miraculous. Their goal is to meet both the physical and spiritual needs of the community by raising up and listening to leaders in that community. Once the leaders have identified the needs we work on the top needs until that top need changes. It is a drastic change from the normal American solution where we come into a community and give them something they may or may not need, like a well, a sewage system, a road or a house. It is also different in that it is mostly development with relief when absolutely necessary. We help the community meet felt needs, not simply meet those needs for the community. This is done through training, micro-lending or just coming along side and helping to solve the biggest problems these communities face.

This is such a simple change, but has made a profound difference in the behaviour in both the community residents and we who are serving that community. It is an amazing process and I'm excited to see the changes in Sweet Home over the coming years.

If you would like to know more about the trip, we have a blog here. It will probably not be added to on our trip, but will be updated on our return.

We have also been looking at ways to bringing these principals to local communities where we server, including Santa Ana, Watts and Mexico.

Wednesday, July 30, 2008

Second Life Physics Scripting - Pinball #23 - llRez and llDie

The next feature is to remove the ball when it hits the back wall and then to have it respawn when the user presses the page up key.

The first problem is that the ball has to remove itself. There doesn't seem to be a way to have a script remove another object.

I first set out to have the ball remove itself when it had a collision with the back wall. The problem with this is that the back wall is linked and the collision name had "pinball 1.4" which may change over time and that this collision only occured once when the ball was first placed on the table since it is all one linked set.

The next solution is to send a message to the ball when the back wall is hit and I did this with the chat system llSay.


if (llDetectedName(0) == "ball")
{
...
//llPushObject(llDetectedKey(0), pos3, <0,0,0>, FALSE);

llSay(1296,"byebye");

}

This was as simple as removing the push and adding the llSay. The next step is to have the ball listen for the message.

integer listen_handle;

default
{
state_entry()
{
listen_handle = llListen( 1296, "backwall", NULL_KEY, "" );
}
listen(integer channel, string name, key id, string message)
{

//llOwnerSay("ball listen, heard = "+name);
if ((channel==1296) && (llToLower(message) == "balldied"))
{
//llOwnerSay("Ball died!");
llDie();
}
}

}

The ball is listening for the object backwall to say "byebye" on channel 1296. At first I thought this would be too slow, but it seems very quick and the ball goes away before bouncing back onto the table.

Make sure you keep a copy of the ball in your inventory because when it dies you don't want to have to recreate the scripts over and over.

The next step is the spawning of the ball (llRez) when the user presses the page up key. I did this in the PayAndKeyboard script in root table object. This is the same script that handles the key presses to move the puck flipper.


RezBall()
{
if (!in_play)
{
in_play = TRUE;

// find the size of the this table
vector sz = llList2Vector(llGetPrimitiveParams([PRIM_SIZE]),0);

vector pos = llGetPos();
//llOwnerSay("pos = "+(string)pos);
vector up = llVecNorm(pos * llGetLocalRot());
up = up * ((sz.z/2) + 0.21); // add the size of the ball
//llOwnerSay("up = "+(string)up);

pos = pos + up;
//llOwnerSay("rez pos = "+(string) pos);

llRezObject("ball",pos,<0,0,0>,ZERO_ROTATION,0);
}
}

I wrote a separate function for the rez ball. This still has problems as I think it rezes the ball too low on the table, but it has a neat effect in that the ball sort of oozes out of the table. It needs looking into at some point.

Next is the event to rez the ball.

control(key id, integer held, integer change)
{
... puck flipper code

// page up key
if ((held&CONTROL_UP) && (change&CONTROL_UP))
{
if (!in_play)
{
llSay(0," here we go!!!");
}
RezBall();
}
}


Then we have to have a way to reset the in_play flag so the user can press the "page up" key again and have the ball rez. This is as simple as adding the listen method the same as we used for the ball. Both the ball and this PayAndKeyboard script will both hear the same message from the backwall.

state_entry()
{

listen_handle = llListen( 1296, "backwall", NULL_KEY, "" );
llSetTimerEvent(30);

... other initialization code
}

listen(integer channel, string name, key id, string message)
{
if ((channel==1296) && (llToLower(message) == "balldied"))
{
llSay(0,"Oops, drain. Press 'Page Up' to start another ball.");
in_play = FALSE;
llSetTimerEvent(30);
}
}

As you can see I also added some timer events. This is so the table is actually doing something when people first walk up to it. A teaser to get people interested. 30 seconds after every drain, then timer goes off. If the ball isn't already in play (because someone pushed page up), then a new ball is spawned.

timer()
{
if (!in_play)
{
llSay(0,"Autoplay starting");
RezBall();
}
llSetTimerEvent(0);
}

Tuesday, July 29, 2008

Second Life Physics Scripting - Pinball #22 - llRezObject ball

I'm beginning to think I can use a different blog entry title and may change to Second Life Game Scripting on the next one since I'm no longer really dealing with physics and have moved onto building the game elements?

The next step is to spawn the ball (llRezObject) when the game starts, and to remove the ball when it hits the back wall. I decided to make the first attempt fairly simple and just rez the ball in the center of the table when the up key is pressed. This seemed pretty straight forward and didn't seem to take very long. Reading up on how to do it took most of the time. Here is the code that I used to rez the ball from inventory.


control(key id, integer held, integer change)
{
... code for the flipper controls ...
if ((held&CONTROL_UP) && (change&CONTROL_UP))
{
if (!in_play)
{
in_play = TRUE;

llSay(0," here we go!!!");

// find the size of the this table
vector sz = llList2Vector(llGetPrimitiveParams([PRIM_SIZE]),0);

vector pos = llGetPos();
llOwnerSay("pos = "+(string)pos);
vector up = llVecNorm(pos * llGetLocalRot());
up = up * ((sz.z/2) + 0.21); // add the size of the ball
llOwnerSay("up = "+(string)up);

pos = pos + up;
llOwnerSay("rez pos = "+(string) pos);

llRezObject("ball",pos,<0,0,0>,ZERO_ROTATION,0);
}
}
}


Adding the ball to inventory was pretty simple. Select the root object and drag the ball from my inventory into the root object's contents. The contents are the same place all the scripts are stored. The first time I did this it took me a while to figure out that you could also store objects in the contents, not just scripts.

Next up I have to change the collision script on the back wall to derez the ball instead of bouncing it back. Then I'll have to send a message to the root prim so I can reset the in_play flag so the person can rez another ball. From there it's a matter of giving each player a set number of balls and keeping track of a high score. Getting closer to a full circle game....

Monday, July 28, 2008

Second Life Physics Scripting - Pinball #21 - Puck Flipper Collisions

Blackjack! No, pinball! I think we have a game! At least the mechanic of a game. This is the first time I've felt like this could actually be a little bit fun to play and I feel like I'm geting closer to something playable. Very cool.

Here is the puck flipper collision script.


collision_start(integer total_number)
{
if (llDetectedName(0) == "ball")
{
// positions are in global coordinates

// find the position of the collision...
vector colpos = llDetectedPos(0);
//llOwnerSay("colpos = "+(string)colpos);

// find the location of the puck prim
vector locpos = llGetPos();
//llOwnerSay("locpos = "+(string)locpos);

// subtract two to get local coordinates on the puck
colpos = locpos - colpos;
//llOwnerSay("colpos = "+(string)colpos);

// divide by the global rotation so we are back
// in the original build orientation
rotation locrot = llGetRot();
colpos = colpos / locrot;
//llOwnerSay("colpos after rot = "+(string)colpos);

// find the size of the wedge
vector sz = llList2Vector(llGetPrimitiveParams([PRIM_SIZE]),0);
//llOwnerSay("puck size = "+(string)sz);
// this size is pre-rotations. We are interested in the Y value

// find the width of the puck
float puckwid = sz.y/4;

// the start of the puck, origin is center so divide by 2
float puckstart = (curval * puckwid) - (sz.y/2);
float puckend = puckstart + puckwid + puck_overlap;
puckstart = puckstart - puck_overlap;

if ((colpos.y>=puckstart) && (colpos.y<=puckend))
{
//llOwnerSay("PUCK IN CURCOL "+(string) curval);

// the dir we what to pushin
vector pushdir = <1,0,0>;
pushdir = pushdir * locrot;
//llOwnerSay("push dir = "+(string)pushdir);

llPushObject(llDetectedKey(0), pushdir, <0,0,0>, FALSE);
}
else
{
// shoot it towards the back wall because
// if it is going slow and they get the puck
// under the ball it seems like it should shoot away
// the dir we what to pushin
vector pushdir = <-1,0,0>;
pushdir = pushdir * locrot;
//llOwnerSay("push dir = "+(string)pushdir);

llPushObject(llDetectedKey(0), pushdir, <0,0,0>, FALSE);
}
}
}


Since the last version I added the checks to see which portion of the puck flipper the ball is actually hitting. Just a simple division by the number of pucks in the texture and then a check to see if it is in the currently active portion of the puck.

After playing with it for a while I added the extra push towards the back wall if it isn't in the puck area. It seemed like I was able to move the puck under the ball a lot and it felt like it should shoot away, but there is really no easy way to tell where the ball is every move of the puck, so I decided to shoot it towards the back wall.

Up next, a ball launcher and a ball killer when it hits the back wall. From there, a game start and game end condition, then maybe some extras besides simple bumpers. Somewhere in there I'll have to move to some real art from the plywood. Very cool.

Friday, July 25, 2008

Second Life Physics Scripting - Pinball #20 - Complete Puck Flipper System

Is this ever going to end? 20 posts already and there still seems like a ton of game code to add. This next step was all about trying to figure out where the ball crossed what I'm now calling the "puck flipper". I just typed it in once and it stuck, but now that I look at it I don't think it's the best name since it seems like the brain really wants to swap the f and p. Maybe it's just my brain? Oh well, it's in the code now.

Removing the flipper and hooking up the new puck flipper to the machine was a pretty simple exercise and only took a few minutes.


Anyway, it was pretty easy to setup the collision system, but actually a little hard to figure out where it crosses when you think about it in larger terms. Look at this first step.


collision_start(integer total_number)
{
if (llDetectedName(0) == "ball")
{
// positions are in global coordinates

// find the position of the collision...
vector colpos = llDetectedPos(0);
//llOwnerSay("colpos = "+(string)colpos);

// find the location of the puck prim
vector locpos = llGetPos();
//llOwnerSay("locpos = "+(string)locpos);

// subtract two to get local coordinates on the puck
colpos = locpos - colpos;
}
}

This is pretty straight forward. Take the positions of the two elements and subtract their positions. This will give you the coordinates local to the puck. If you divide the y size by 4, this gives you the size of each puck positions. Multiply by the current puck and position and add a little bit in each direction and you can then compare that to the Y collision position.

Easy you say. Until you start to rotate the pinball machine around. I've built it aligned along the X axis, but if you rotate it 90 degrees into the Y axis then all the calculations will no longer work! You have to take this rotation into account. This next piece shows that code.


collision_start(integer total_number)
{
if (llDetectedName(0) == "ball")
{
// find the position of the collision...
vector colpos = llDetectedPos(0);
//llOwnerSay("colpos = "+(string)colpos);

// find the location of the puck prim
vector locpos = llGetPos();
//llOwnerSay("locpos = "+(string)locpos);

// subtract two to get local coordinates on the puck
colpos = locpos - colpos;
llOwnerSay("colpos = "+(string)colpos);

// divide by the global rotation so we are back
// in the original build orientation
rotation locrot = llGetRot();
colpos = colpos / locrot;
llOwnerSay("colpos after rot = "+(string)colpos);
}
}

Don't think I'm some sort of genuis and this just fell out of my brain. I spent at least an hour tweaking to get this right. For the longest time I had what I originally wrote colpos = colpos * locrot;. After I got my view port inside the pinball machine and dropped the ball at both edges a bunch of times I realized the Y values were giving opposite results when the table was rotated. I figured that using division of the rotation might swap the signs and sure enough it did and now I can move on.

I really like the puck mechanic so far and think it will be really effective. I think it will be even better if I used it with more than 4 positions. Given a bunch of positions it might even look just like a pong puck and allow for some really cool game mechanics. I've not seen anyone use this yet, so I'm excited to see this in action.

Thursday, July 24, 2008

Second Life Physics Scripting - Pinball #19 - Puck Flipper Mechanic

Since the flipper mechanic just wasn't going to work I've decided for a bar at the bottom of the table where you can move what area is currently active. Sort of like pong, but more primitive. I'm hoping that people will identify with it as a puck because it's attached to a pinball machine. I wanted to use a changing texture on a single primitive to increase the speed changes since there aren't the same delays for texture changes as there are for position and rotation. This is some quick temporary art I came up with as the texture.



The image is split into four positions that will bounce the ball if it hits in the current position. I offset a little into the other colors to make it a little easier and allow for you to hit things that were right on the borders of the colors.

I'm sliding this around on a cube face much like I did the number texture. There was nothing fancy when figuring out the offsets. Hit and miss and tweaking the u/v numbers on the texture editing. I did only apply this texture to a single side of a rectangular cube. You do that by toggling the "select texture" radio button on the editor. Here is the script that changes which color is currently active.


// texture offsets
// h = 0;
// v =
// 0.375 (red=1)
// 0.125 (blue=2)
// -0.125 (green = 3)
// -0.375 (yellow = 4)
integer curval = 1;
doOffset()
{
//llOwnerSay("offset ="+(string)ftmp);
if (curval<0)
{
curval = 3;
}
else if (curval>3)
{
curval = 0;
}
float foffset = 0.375;
if (curval == 1)
{
foffset = 0.125;
}
else if (curval == 2)
{
foffset = -0.125;
}
else if (curval == 3)
{
foffset = -0.375;
}
llOffsetTexture(0.0,foffset,ALL_SIDES);
}

default
{
state_entry()
{
doOffset();
}

touch_start(integer total_number)
{
curval++;
doOffset();
}
}


Wednesday, July 23, 2008

Second LIfe Physics Scripting - Pinball #18 - Integrated Flipper

Okay, after all that work on the flipper I tried to integrate it anyway just to see if the mechanice was salvagable. It was a complete bust. The collisions did not occur properly while the rotation was in progress and overall system did not behave at all like a pinball flipper. I tried to add the llPushObject code to the flipper when it was on only, but then trying to trap the ball with the flipper had all kinds of issues. Overall, it was a complete waste of time.


Just for fun, here is the complete flipper_rotate_listener script.


integer MSG_RIGHT_ON_NUM = 5115;
integer MSG_RIGHT_OFF_NUM = 5116;
integer MSG_LEFT_ON_NUM = 5117;
integer MSG_LEFT_OFF_NUM = 5118;

rotation start_rot;
vector start_pos;
integer orig_axis_flag = FALSE;
vector orig_axis;

integer on=FALSE;
integer cur_rotation=0;
integer max_rotation = -60;
integer rotation_tick = -20;
float timer_gap = 0.1;

float bumpspeed_waiting = 0.2;
float bumpspeed_on = 0.62;
float bumpspeed_off = 0.01;

default
{
state_entry()
{
start_rot = llGetLocalRot();
start_pos = llGetLocalPos();

state waiting;
}
}

state waiting
{
state_entry()
{
}

link_message(integer from, integer msg_id, string str, key id)
{
if (msg_id == MSG_RIGHT_ON_NUM)
{
llOwnerSay("flipper rotate on");
state rotate_on;

}
else if (msg_id == MSG_RIGHT_OFF_NUM)
{
state rotate_off;
}
}

} // state waiting

state rotate_on
{
state_entry()
{
llSetTimerEvent(timer_gap);
}

timer()
{
if (((rotation_tick>0) && (cur_rotation>max_rotation)) ||
((rotation_tick>0) && (cur_rotation>max_rotation)))

{
cur_rotation = cur_rotation + rotation_tick;

// what is currently pointing up in the z direction
// was originally in the -y direction.
if (!orig_axis_flag)
{
orig_axis_flag = TRUE;
orig_axis = llVecNorm(<0,-1,0> * llGetLocalRot());
}

// a rotation around the parents z normal
//rotation z_45 = make_quaternion(parent_norm,rotation_tick*DEG_TO_RAD);
rotation z_rot = llAxisAngle2Rot(orig_axis,rotation_tick*DEG_TO_RAD);

// we need to find the axis which is the heal of the wedge
// the original orientation of the flipper wedge is <0,0,1>
// i use -1 to point at the heal of the wedge
vector norm = <0,0,-1>;
// rotate that norm so it points towards the point of the wedge
norm = llVecNorm(norm * llGetLocalRot());
// find the size of the wedge
vector sz = llList2Vector(llGetPrimitiveParams([PRIM_SIZE]),0);
// get the center location of the wedge
vector flipper_pos = llGetLocalPos();
// find the offset distance from the center
vector offset = norm * (sz.z / 2);
// add that offset to the flipper_pos to get the axis
vector rot_axis = flipper_pos + offset;

// calculate the new rotation by adding the z axis rotation
// z_rot is the old name it is really z_plusang
rotation new_rot = llGetLocalRot()*z_rot;
// http://wiki.secondlife.com/wiki/Rotation
// set the new local rotation
llSetLocalRot(new_rot);

// lsl wiki solution for non center rotation
vector new_pos = rot_axis + ((flipper_pos - rot_axis) * z_rot);
llSetPos(new_pos);
}
else
{
// stop the timer and wait for the off event
llSetTimerEvent(0);
}
} // timer

collision_start(integer total_number)
{
//llOwnerSay("Collision start");
//llOwnerSay(llDetectedName(0) + " collided with me!");
if (llDetectedName(0) == "ball")
{
// need to find the direction from the backwall to the ball
vector pos = llGetPos();
list a = llGetObjectDetails(llDetectedKey(0), ([OBJECT_POS]));
vector pos2 = llList2Vector(a,0);
//vector pos3 = pos-pos2;
vector pos3 = llVecNorm(pos2-pos);
llOwnerSay("bump pos3 = "+(string)pos3);
//if (pos3.z>0) // it's not behind us
//{
//pos3.y = pos3.y*2;
//integer ra = (integer) llFrand(1.0)-1;

pos3 = pos3 * bumpspeed_on;
llOwnerSay("bump mag = "+(string)llVecMag(pos3));

//float mag = llVecMag(pos3);
//if (mag>maxspeed)
//{
// float magdev = maxspeed / mag;
// pos3 = pos3 * magdev;
//
// mag = llVecMag(pos3);
//}
//llOwnerSay("sidewall = "+(string)pos3);

llPushObject(llDetectedKey(0), pos3, <0,0,0>, FALSE);
//}

}
}

link_message(integer from, integer msg_id, string str, key id)
{

if (msg_id == MSG_RIGHT_OFF_NUM)
{
llSetTimerEvent(0);
state rotate_off;
}
}
} // state rotate_on

state rotate_off
{
state_entry()
{
// already in local coords since child prim
llSetPos(start_pos);
llSetLocalRot(start_rot);
cur_rotation = 0;
state waiting;
}
link_message(integer from, integer msg_id, string str, key id)
{

if (msg_id == MSG_RIGHT_ON_NUM)
{
state rotate_on;
}
}
} // state rotate_off


And the pay and keyboard script in the pinball parent.


integer right_flipper = -1;
integer left_flipper = -1;

integer MSG_RIGHT_ON_NUM = 5115;
integer MSG_RIGHT_OFF_NUM = 5116;
integer MSG_LEFT_ON_NUM = 5117;
integer MSG_LEFT_OFF_NUM = 5118;

default
{
state_entry()
{
integer current_link_nr = llGetNumberOfPrims();
// Check if it's more than one
if (1 < current_link_nr)
{
// avatars sitting on us get added at the end, so subtract...
while (llGetAgentSize(llGetLinkKey(current_link_nr)))
--current_link_nr;

while(current_link_nr>0)
{
if (llGetLinkName(current_link_nr)=="right_flipper")
{
llOwnerSay("found right_flipper: "+(string)current_link_nr);
right_flipper = current_link_nr;
}
else if (llGetLinkName(current_link_nr)=="left_flipper")
{
llOwnerSay("found right_flipper: "+(string)current_link_nr);
left_flipper = current_link_nr;
}
current_link_nr--;
}
}
}

run_time_permissions(integer perm)
{
// permissions dialog answered
if (perm & PERMISSION_TAKE_CONTROLS)
{
// we got a yes
// take left, right, up and down controls
llTakeControls(CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE);
}
}

touch_start(integer total_number)
{
llSay(0, "Would you like to play? I'll need to take over your keyboard.");
llSay(0, "This doesn't work yet, it's still under construction.");
llSay(0, "The only key that works is 'd' and even then, the flipper is off the actual table, getting closer and there is only one of them...");
llSay(0, "To get your keys back simply click on the Release Keys button just above the Fly button");

integer perm= llGetPermissions();

if (!perm&PERMISSION_TAKE_CONTROLS)
{
llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS);
// get permission to take controls
}
else
{
llSay(0,"We have permission to take control...trying");
llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS);
//llTakeControls(CONTROL_LEFT| CONTROL_RIGHT | CONTROL_UP | CONTROL_DOWN,
// TRUE, TRUE);
}
}
control(key id, integer held, integer change)
{
//llSay(0, "control id="+(string)id);
//llSay(0, " held="+(string)held+" change="+(string)change);
//llSay(0, " ROT_RIGHT"+(string)CONTROL_ROT_RIGHT);
if ((held&CONTROL_ROT_RIGHT) && (change&CONTROL_ROT_RIGHT))
{
//llSay(0," right flipper on");
llMessageLinked(right_flipper, MSG_RIGHT_ON_NUM, "", NULL_KEY);

}
else if (((held&CONTROL_ROT_RIGHT)==0) && (change&CONTROL_ROT_RIGHT))
{
//llSay(0," right flipper off");
llMessageLinked(right_flipper, MSG_RIGHT_OFF_NUM, "", NULL_KEY);
}

}
}

Tuesday, July 22, 2008

Second Life Physics Scripting - Pinball #17 - Flipper Rotations Working

Rotations, what a nightmare. A nightmare of my own making, but I finally got the problem solved and it looks like I mostly had confused myself, but after lots of reading and a thousand tests I finally got the flipper to rotate on the test table.



Here is the final code for the timer routine that rotates the flipper in x number of steps.


timer()
{
if (((rotation_tick>0) && (cur_rotation<max_rotation)) ||
((rotation_tick<0) && (cur_rotation>max_rotation)))

{
cur_rotation = cur_rotation + rotation_tick;

// what is currently pointing up in the z direction
// was originally in the -y direction.
if (!orig_axis_flag)
{
orig_axis_flag = TRUE;
orig_axis = llVecNorm(<0,-1,0> * llGetLocalRot());
}

// a rotation around the parents z normal
//rotation z_45 = make_quaternion(parent_norm,rotation_tick*DEG_TO_RAD);
rotation z_45 = llAxisAngle2Rot(orig_axis,rotation_tick*DEG_TO_RAD);

// we need to find the axis which is the heal of the wedge
// the original orientation of the flipper wedge is <0,0,1>
// i use -1 to point at the heal of the wedge
vector norm = <0,0,-1>;
// rotate that norm so it points towards the point of the wedge
norm = llVecNorm(norm * llGetLocalRot());
// find the size of the wedge
vector sz = llList2Vector(llGetPrimitiveParams([PRIM_SIZE]),0);
// get the center location of the wedge
vector flipper_pos = llGetLocalPos();
// find the offset distance from the center
vector offset = norm * (sz.z / 2);
// add that offset to the flipper_pos to get the axis
vector rot_axis = flipper_pos + offset;

// calculate the new rotation by adding the z axis rotation
// z_45 is the old name it is really z_plusang
rotation new_rot = llGetLocalRot()*z_45;
// http://wiki.secondlife.com/wiki/Rotation
// set the new local rotation
llSetLocalRot(new_rot);

// lsl wiki solution for non center rotation
vector new_pos = rot_axis + ((flipper_pos - rot_axis) * z_45);
llSetPos(new_pos);
}
else
{
// stop the timer and wait for the off event
llSetTimerEvent(0);
}
} // timer

It turns out that the basic problem I was having was that I didn't think there was a llSetLocalRot because I already knew there wasn't a llSetLocalPos. So that final call to llSetLocalRot was llSetRot for the longest time and this added a little extra y rotation which caused the flipper to lean in the wrong direction. This was a ton of time to actually find this problem, but rotation can be like that and this is really just another bug in a long development process.

The crazy part is that I continued to work on this even though I knew the mechanic was not going to work. The flipper just takes too long to rotate each step and I don't want a flipper that only has two angles of bounce. Very annoying that there is a lot of work there and the api/engine doesn't support the mechanic I want to use, but then this is the way in software development. You have to make your designs fit the platforms you use and hope people make good platforms. SL is good, but there are a number of changes I would like to see made, but I don't think that is any different than any other api/platform.

I'd rather not see them do the kitchen sink stuff that was done with Java and basically ruined a very nice small system. Then again, they ignored the client side and just gave it up to flash which was a huge mistake that can't really be addressed at this point because now they made the foot print way too big to be a client side platform. I still love Java, I just think Schwartz badly mismanaged it just like he has mismanaged Sun. Sorry for the short rant.

So I think I'm going to use textures to do flippers flat near the back of the machine. We'll see how that turns out next.

Monday, July 21, 2008

Second Life Physics Scripting - Pinball #16 - Flipper rotations using Quaternions

I messed around a little bit with the flipper when "test" table was tilted and got a little frustrated. I thought to myself, this would be a lot easier if this system had Quaternions instead of just a simple llEuler2Rot function. I went to look for a solution and sure enough, this is exactly what the Linden's did originally. The lslwiki even has a special page for it.

http://www.lslwiki.net/lslwiki/wakka.php?wakka=quaternions

Quaternions may sound scary, but they are really very simple. You create a unit vector (a vector of length=1) and the quaternion is a rotation around that vector. Pretty simple for a scary sounding word.

Oh, and after some more digging and using the make_quaternion function listed on the lslwiki, I found the note that this is exactly the same as the llAxisAngle2Rot function included in Second Life. Exactly what I was looking for and named a lot less scary that quaternion.

Friday, July 18, 2008

Second Life Physics Scripting - Pinball #15 - Flipper rotation and llSetPos problems

Spent a ton of time trying to get the flipper to rotate off axis.

At one point the llSetPos stopped working and I went back and created a test system to try and figure it out.



I kept calling llSetPos and nothing would move at all. This had to do with the requirement of calling llSetPos if you want to rotate off axis. Being my own worst enemy, I had done a test and changed some calls from llGetLocalPos to llGetPos. While there is no llSetLocalPos, the change from llGetLocalPos was what caused the problem. Since the flipper is a linked prim, the coordinates must be changed in local coordinates otherwise the world coordinates were off the map and nothing moved.

Then I also found the problem with the off axis rotation. Here is the final piece of code, with all of my debugs and junk code just so you can see how many interation tests went into it. This is from the link_message method outlined in the previous post.


// a rotation of 45 degrees around the z-axis
rotation z_45 = llEuler2Rot( <0, 0, -45 * DEG_TO_RAD> );
// the tilt of the table
rotation y_352 = llEuler2Rot( <0, 352 * DEG_TO_RAD, 0>);

// the original orientation of the flipper wedges is <0,0,1>
// i use -1 to point at the heal of the wedge
vector norm = <0,0,-1>;
// rotate that norm so it points towards the point of the wedge
norm = llVecNorm(norm * llGetLocalRot());
//norm = llVecNorm(norm * llGetRot());

// find the size of the wedge
vector sz = llList2Vector(llGetPrimitiveParams([PRIM_SIZE]),0);
llOwnerSay("size = "+(string)sz);
vector flipper_pos = llGetLocalPos();
vector offset = norm * (sz.z / 2);
vector rot_axis = flipper_pos + offset;
//vector rot_axis = start_pos + offset;

llOwnerSay("flipper pos="+(string) flipper_pos);
llOwnerSay("rot_axis ="+(string) rot_axis);
llMessageLinked(dbg_marker, 1,(string)rot_axis, NULL_KEY);
//llOwnerSay("distance from center to heal = "+(string)offset);
llOwnerSay("get rot = "+(string)llGetRot());
llOwnerSay("get local rot = "+(string)llGetLocalRot());
rotation new_rot = llGetLocalRot() * z_45; // compute global rotation
//new_rot = new_rot * norm;
// http://wiki.secondlife.com/wiki/Rotation
// find the rotated norm

vector rotated_offset = offset * new_rot;
llOwnerSay("new rot = "+(string)new_rot);
llSetRot(new_rot);

// ll wiki method
//vector rotatedOffset = offset * z_45; // rotate the offset to get the motion caused by the rotations
//vector newPos = llGetPos() + (offset - rotatedOffset) * llGetRot(); // move the prim position by the rotated offset amount
//rotation newRot = rot6X * llGetRot();

//llOwnerSay(" cur pos = "+(string)llGetLocalPos());
// lsl wiki solution
vector new_pos = rot_axis + ((flipper_pos - rot_axis) * z_45);
//vector new_pos = rot_axis + ((flipper_pos - rot_axis) * new_rot);
//vector new_pos = rot_axis + (offset * new_rot);
//vector new_pos = llGetLocalPos() - (offset * llGetLocalRot());
//vector newPos = llGetLocalPos() + (offset - rotatedOffset) * llGetRot()
//new_pos.x = new_pos.x + 2;
llOwnerSay(" new pos = "+(string) new_pos);
llSetPos(new_pos);
//llOwnerSay(" after = "+(string)llGetLocalPos());


After all that, the problem ended up being this one line.


vector new_pos = rot_axis + ((flipper_pos - rot_axis) * z_45);
//vector new_pos = rot_axis + ((flipper_pos - rot_axis) * new_rot);

As you can see I was multiplying the offset by the new_rot which includes some of the initial rotations of the flipper. When all I really had to do is rotate it by the z_45 which is the change in rotation. This doesn't include the slight rotation of the table, but I'll be adding that as well soon. For now I'm going to do some more work on this test block first.

This still doesn't work all that well and I don't think it will behave like a pinball machine because the inbetween stages will not hit the ball properly. I'm going to make this a lot more complicated and fold it into a timer method and try rotating in increments. Wish me luck.

Thursday, July 17, 2008

Second Life Physics Scripting - Pinball #14 - Flipper Rotation Off Axis Issue

I've spent way too much time trying to get the flipper to rotate properly. I'm not even sure if it will work properly when I do get it to rotate, but now I'm on a mission to get this working.

Basically there are two rotation pages.
http://www.lslwiki.net/lslwiki/wakka.php?wakka=rotation
http://wiki.secondlife.com/wiki/Rotation

The rotation from the center of the flipper works great, the off axis rotation is even harder. I think I spent a lot of time on one flipper that was rotated differently than I thought so I started over with a new flipper and the same code.

Basically, I need to calculate the base/heal of the flipper. I do this by looking at the initial orientation of the tip of the flipper and it was straight up in the z direction. I take the negative z vector and multiply it by the starting rotation which points me in the direction of the heal and convert it to a unit vector. I then multiply that unit vector by half of the z size which should give the the starting location of the heal offset from the center of the flipper. Tricky. That will be the point of rotation and that is what I've struggled with so far.

Both wiki's say it should be fairly straight forward, but I keep getting a rotational axis that is missing the mark.

Here is the current (not working code with a ton of test code). The test code is not offset so I can find it more easily.

link_message(integer from, integer msg_id, string str, key id)
{
if (msg_id == MSG_RIGHT_ON_NUM)
{
//llOwnerSay("right flipper on");
//llSetLocalRot(end_rot);
// a rotation of 45 degrees around the x-axis
rotation x_45 = llEuler2Rot( <0, 0, -45 * DEG_TO_RAD> );


// the original orientation of the flipper wedges is <0,0,1>
// i use -1 to point at the heal of the wedge
vector norm = <0,0,-1>;
// rotate that norm so it points towards the point of the wedge
norm = llVecNorm(norm * llGetLocalRot());

// find the size of the wedge
vector sz = llList2Vector(llGetPrimitiveParams([PRIM_SIZE]),0);
llOwnerSay("size = "+(string)sz);
vector offset = norm * (sz.z / 2);
vector rot_axis = start_pos + offset;
llMessageLinked(dbg_marker, 1,(string)rot_axis, NULL_KEY);
llOwnerSay("distance from center to heal = "+(string)offset);
llOwnerSay("get rot = "+(string)llGetRot());
rotation new_rot = llGetLocalRot() * x_45; // compute global rotation
//new_rot = new_rot * norm;
// http://wiki.secondlife.com/wiki/Rotation
// find the rotated norm
vector rotated_offset = offset * new_rot;
llOwnerSay("new rot = "+(string)new_rot);
llSetLocalRot(new_rot);

llOwnerSay(" cur pos = "+(string)llGetLocalPos());
vector offsettimesrot = (-offset*new_rot);
llOwnerSay("offset times rot ="+(string)offsettimesrot);
vector new_pos = rot_axis + (offset * new_rot);
//vector new_pos = llGetLocalPos() - (offset * llGetLocalRot());
//vector newPos = llGetPos() + (offset - rotatedOffset) * llGetRot()
llOwnerSay(" new pos = "+(string) new_pos);
llSetPos(new_pos);

}
else if (msg_id == MSG_RIGHT_OFF_NUM)
{
//llOwnerSay("right flipper off");
llSetLocalRot(start_rot);

// already in local coords since child prim
llSetPos(start_pos);
}
}

At this point I'm having trouble visualizing the actual origin that is being rotated, so I've created a thin black cylinder called "dbg_marker" and I'm going to position that what I calculate to be the origin of the heal.


As usual I can find the marker by traversing the link list.


test_find_marker()
{
integer current_link_nr = llGetNumberOfPrims();
// Check if it's more than one
if (1 < current_link_nr)
{
// avatars sitting on us get added at the end, so subtract...
while (llGetAgentSize(llGetLinkKey(current_link_nr)))
--current_link_nr;

while(current_link_nr>0)
{
if (llGetLinkName(current_link_nr)=="dbg_marker")
{
llOwnerSay("found dbg_marker: "+(string)current_link_nr);
dbg_marker = current_link_nr;
}
current_link_nr--;
}
}
}

The code to actually move the marker is yet another link message and some code in the marker. There really isn't a lot of api code to move another object to a specific location, you have to send a message to the object and have it move itself. Sort of a pain, but it forces you to separate out your logic which is a good thing.

Here is the code placed in the dbg_marker to move it to the location.

default
{
state_entry()
{

}

link_message(integer from, integer msg_id, string str, key id)
{
if (msg_id == 1)
{
vector vpos = (vector)str;
llOwnerSay("dbg_marker position"+(string)vpos);

llSetPos(vpos);
}
}
}


Then this final screen shot which shows that the location I calculated with all that crazy math was actually precisely on target.


It is obviously very very close to working, I just have a transformation out of order or something... Hmmm.... Welcome to my world.

Wednesday, July 16, 2008

Second Life Physics Scripting - Pinball #13 - Flipper Rotation Issues

I've got the first flipper movement. It doesn't actually orient correctly or rotate around the correct axis, but it is movement and the rest is just some extra math.

I took the start rotation and end rotation ligning up the flipper where I wanted it, then running the script to see the rotation. This doesn't seem to work as the rotation is not the same as how I lined it up, but it's close and late at night and deserves to be a stopping point.


Here is the script in the flipper.

integer MSG_RIGHT_ON_NUM = 5115;
integer MSG_RIGHT_OFF_NUM = 5116;
integer MSG_LEFT_ON_NUM = 5117;
integer MSG_LEFT_OFF_NUM = 5118;

rotation start_rot = <0.35240, 0.55950, 0.62163, 0.41994>;
rotation end_rot = <0.66946, 0.22398, 0.36898, 0.60458>;

default
{
state_entry()
{
//start_rot = llGetRot();
llOwnerSay("right rot = "+(string)llGetRot());
llSetRot(start_rot);
}

link_message(integer from, integer msg_id, string str, key id)
{
if (msg_id == MSG_RIGHT_ON_NUM)
{
llOwnerSay("right flipper on");
llSetRot(end_rot);
}
else if (msg_id == MSG_RIGHT_OFF_NUM)
{
llOwnerSay("right flipper off");
llSetRot(start_rot);
}
}
}


Here is the code in the main object that takes over the controls.

integer MSG_RIGHT_ON_NUM = 5115;
integer right_flipper = -1;

integer MSG_RIGHT_OFF_NUM = 5116;
integer MSG_LEFT_ON_NUM = 5117;
integer MSG_LEFT_OFF_NUM = 5118;

state_entry()
{
integer current_link_nr = llGetNumberOfPrims();
// Check if it's more than one
if (1 < current_link_nr)
{
// avatars sitting on us get added at the end, so subtract...
while (llGetAgentSize(llGetLinkKey(current_link_nr)))
--current_link_nr;

while(current_link_nr>0)
{
if (llGetLinkName(current_link_nr)=="right_flipper")
{
llOwnerSay("found right_flipper: "+(string)current_link_nr);
right_flipper = current_link_nr;
llMessageLinked(right_flipper, MSG_SET_PARENT_ROT, (string)llGetRot(), NULL_KEY);

}
current_link_nr--;
}
}
}

control(key id, integer held, integer change)
{
//llSay(0, "control id="+(string)id);
//llSay(0, " held="+(string)held+" change="+(string)change);
//llSay(0, " ROT_RIGHT"+(string)CONTROL_ROT_RIGHT);
if ((held&&CONTROL_ROT_RIGHT) && (change&&CONTROL_ROT_RIGHT))
{
//llSay(0," right flipper on");
llMessageLinked(right_flipper, MSG_RIGHT_ON_NUM, "", NULL_KEY);

}
else if (((held&&CONTROL_ROT_RIGHT)==0) && (change&&CONTROL_ROT_RIGHT))
{
//llSay(0," right flipper off");
llMessageLinked(right_flipper, MSG_RIGHT_OFF_NUM, "", NULL_KEY);
}
}

As you can see I'm using linked messages to tell the flipper to rotate. This is normal. Also, the control code is a separate script in the root object. Now my root object has 8 scripts. Blank Collision, Bumper, LinkNanny, LinkNanny-bumper, LinkNanny-sidewall, PayAndKeyboard, RootNanny, side-wall-bounce. LSL almost requires that you break up your code into different and more manageable scripts.

Tuesday, July 15, 2008

Simple Signpost

I created a simple signpost to advertise the blog. Here is a screenshot.


Here is the simple script that dumps the url into chat and allows them to click on it to view the page.


default
{
state_entry()
{
}

touch_start(integer total_number)
{
llSay(0, "For more information on this build, visit:");
llSay(0,"http://dev360.blogspot.com");
}
}

Monday, July 14, 2008

Second Life Physics Scripting - Pinball #12 - Keyboard Control

I was finally able to trap the keyboard and I wanted to post a code snipet because it should show it in a fairly simple way.
default
{
state_entry()
{
}

// this is called by llRequestPermission
run_time_permissions(integer perm)
{
// permissions dialog answered
if (perm & PERMISSION_TAKE_CONTROLS)
{
// we got a yes
// take up and down controls
llTakeControls(CONTROL_ROT_LEFT
CONTROL_ROT_RIGHT
CONTROL_UP
CONTROL_DOWN,
TRUE, FALSE);
}
}

touch_start(integer total_number)
{
llSay(0, "Would you like to play? I'll need to take over your keyboard.");
llSay(0, "This doesn't work yet, it's still under construction.");

integer perm= llGetPermissions();

if (!perm&PERMISSION_TAKE_CONTROLS)
{
// get permission to take controls
// this was changed after the blog entry
// it needs to be called with detectedKey. During debugging
// you might still want to use llGetOwner to avoid others interupting.
llRequestPermissions(llDetectedKey(0), PERMISSION_TAKE_CONTROLS);
//llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS);
}
else
{
llSay(0,"We have permission to take control...trying");
// this was changed after the blog entry
// it needs to be called with detectedKey. During debugging
// you might still want to use llGetOwner to avoid others interupting.
llRequestPermissions(llDetectedKey(0), PERMISSION_TAKE_CONTROLS);
//llRequestPermissions(llGetOwner(), PERMISSION_TAKE_CONTROLS);
// the sl wiki says that you should always request permission
//llTakeControls(CONTROL_LEFT
// CONTROL_RIGHT
// CONTROL_UP CONTROL_DOWN,
// TRUE, TRUE);
}
}
control(key id, integer held, integer change)
{
llSay(0, "control id="+(string)id);
llSay(0, " held="+(string)held+" change="+(string)change);
}
}
These are the two wiki entries I used to get this done.
http://wiki.secondlife.com/wiki/LlTakeControls
http://www.lslwiki.net/lslwiki/wakka.php?wakka=llTakeControls

One thing I don't like is that it only seams like you can get information on keys that are already mapped to movement. In this case a,w,s,d,e,c,arrow keys and page up/down. This seems very limiting, but I think I can make it work. Seems like an oversite that would have been easy to allow for early on in the development process and I have trouble understanding any reason for this other than a lack of vision in regards to in world controls.

Friday, July 11, 2008

Second Life Physics Scripting - Pinball #11 - More Collision Issues

I found another stuck ball on the top wall.



I also noticed that when I moved the glass sideways to get to the ball, the ball moved farther into the wall and was then sticking out about half way. This is even though the ball was not really near the glass since it was about halfway up the wall. Very strange behavior.

I also saw two cases where the ball was stuck on top of one of the bumpers wedged between the glass. I need to make the glass a little lower or the bumpers a little higher. I'll probably opt to make the bumpers higher as there will be problems with the curve where the glass meets not leaving enough room for the ball if I make the glass lower.

I also noticed that the scoreboard stopped functioning. I had to reset the scripts within the scoreboard to get it going again. This might have been cause by a mistaken "take" of the the pinball machine into my inventory then an extraction back out. It was stuck on the number from around that time and I didn't really check to see if it was still working. I'll keep an eye on this one.

I ran some tests on larger balls and smaller push vectors, but then the balls did not move well at all. I think I'm making a mistake and not taking the mass of the ball into account when pushing it away from the walls and bumpers. Need to look into that as well.


Made some good progress on the keyboard code and need to write up a post for that as well.

Fun.

Thursday, July 10, 2008

Second Life Physics Scripting - Pinball #10 - Collision Tweaking

I just checked in to see where the counter is and to make sure the system was still running. Nothing works better than time to test out a piece of code.

Problem, but I don't think it's a big one or one that can't be fixed fairly easily.



As you can see the ball is embedded into the wall of the top curve. The top curve is a hollow cylinder cut in half. But there is good news too.


The counter reached 31871 before there was a problem. I think I'll have to do two things to fix this. Slow the ball down a little bit more, remeber I said I had it a little larger than the ball size itself and that's probably why it was able to get inside the cylinder wall. To keep things simple, I might want to change the top wall to a flat surface anyway and make it look more curved with the final art.

There was one other observed problem. I left the test code in the scoreboard and the individual texture numbers so if someone clicks on them they increment. Just need to remove that test code.

I'm not sure if I've shown it with all the added bumpers yet? There were a bunch of small problems that were fixed. When I copied the bumpers a bunch of them were not working. I did a bunch of little tests and nothing seemed to work, then I noticed they were not running. I started to edit the scripts individually and turn them on, but then just selected them all and then selected 'Tools/Set Scripts to Running In Selection'. That was a pretty easy fix.

Also, the glass on top was a little too high/low at the curve and it was keeping the ball from getting into the upper sections. I moved them until they were properly aligned. It's all about using the alt and alt/ctrl keys to get a bunch of different perspectives when trying to position stuff.

When I was fixing the bumpers and had the glass off the top, the ball did leap off the table and I had to go searching for it way off in the distance. When I found it I just took it into my inventory, then I think I brought back out the wrong one since it had a really low minimum speed. When I showed it to someone the ball got to going way too slow so I edited it and increased the minimum.

Lots of tweaking.

Next I think I need to start working on the interactivity. I'm going to do some looking at mouse look and figure out how people take over the keyboard, then I'll be ready to add a bunch of interactive buttons attached to keys. At first I think it will just be a couple of flippers, but then I may go a little crazy with it.

Wednesday, July 9, 2008

Second Life Physics Scripting - Pinball #9 - Scoreboard Extension

I next extended the score board to have more digits. This was pretty simple, just copying the numbers and then copying and pasting parts of the scripts to add the extra digits. I first did this as a standalone scoreboard, then moved it onto the pinball machine.

Tuesday, July 8, 2008

Second Life Physics Scripting - Pinball #8 - Script Management

I was starting to do some more development on the walls and bumpers and started finding that managing the scripts in only four walls and three bumpers was going to be a problem, especially when I was planning on adding a bunch more of each in the next few iterations.

I went back and added a LinkNanny system described in the Tic-Tac-Toe Tutorial on the LSLWiki.

This was a bit of work and it took some tweaking to get it right. I ended up creating a different LinkNanny for the bumpers and a separate one for the walls. This allowed me to have separate sets of scripts for each. I was thinking it might be better to keep all this information in the RootNanny and do the distribution based on the prim name, but it was already setup this way so I'm going to just go with it.

My biggest problem with this is when you want to distribute out the latest scripts with "/1 update" it takes a long time. I've been editing directly on one of the objects until I get it right, then copying the script to the root object and the using "/1 update" to distribute them out. It takes too long to be useful in very small changes and my stuff is always so buggy that it takes a million small changes to get even the simpliest stuff working.

Monday, July 7, 2008

Second Life Physics Scripting - Pinball #7 - Linked Object Collision Problems

Linked objects caused some interesting problems with collisions for me. On the physics test from the previous post, I had the back wall which pushed the ball unlinked from the rest of the test. When I joined them together it caused the ball to stop at the bottom like it missed a collision and then would not go again until the ball bumped itself.

This wiki on collisions helped me understand the problem.

http://www.lslwiki.net/lslwiki/wakka.php?wakka=collision_start

The problem being that the back wall became the root prim (the one selected last before the Tools/Link was selected). This means it was getting collision events for both the middle wall collision and the base of the system as the same event. Meaning that the collision never stopped so there was no need to send another collision event. I had to add a blank start_collision method to both the middle wall and the table prims. That fixed the problem.

Wednesday, July 2, 2008

Second Life Physics Scripting - Pinball #6 - Collision Tester


During this process I created a simple test of the ball bouncing through an object. It has a back wall script on the bottom wall which pushes the ball. I could move the middle wall closer and closer to test for the ball bouncing through at different speeds. I left it for a couple of days to test my theories and the speed change I made fixed the problems.

Here is the code for the back wall.


float maxspeed = 0.8;
default
{
collision_start(integer total_number)
{
//llOwnerSay("Collision start");
//llOwnerSay(llDetectedName(0) + " collided with me!");
if ((llDetectedName(0) == "ball") || (llDetectedName(0) == "ball2"))
{
// need to find the direction from the backwall to the ball
vector pos = llGetPos();
list a = llGetObjectDetails(llDetectedKey(0), ([OBJECT_POS]));
vector pos2 = llList2Vector(a,0);
pos.y = pos2.y; // so we don't get any side to side movement
vector pos3 = pos2-pos;
//llOwnerSay("pos = "+(string)pos);
//llOwnerSay("pos2="+(string)pos2);
pos3.x = pos3.x*1.5;
pos3.z = pos3.z*1.5;
float mag = llVecMag(pos3);
if (mag>maxspeed)
{
float magdev = maxspeed / mag;
pos3 = pos3 * magdev;

//mag = llVecMag(pos3);
//llOwnerSay("------adjusted mag down to "+(string)mag);
}
//llOwnerSay("pos3 = "+(string)pos3);
//llOwnerSay("mag3 = "+(string)llVecMag(pos3));
integer ra = (integer) llFrand(1.0)-1;
//pos3.y = pos3.y*ra;
//pos3.y = pos3.y*8;
//llPushObject(llDetectedKey(0), <8,0,1>, <0,0,0>, FALSE);
llPushObject(llDetectedKey(0), pos3, <0,0,0>, FALSE);
}
//llOwnerSay("Collision done");
}
}


The code in the ball was the same as the pinball I showed previously. It really just jiggled once in a while.

Tuesday, July 1, 2008

Second LIfe Physics Scripting - Pinball #5 - Fix Sticky Ball

The problem with the automatic ball moving script was the granularity of the timer event. Once I fixed that (dropped it to 0.01) it when haywire like I expected it would because I was never letting it slow down. I changed it to have a minimum speed and back off the timer to ever half second. This is what I ended up with.
float movebump = 0.1;
float maxspeed = 0.42;
float minspeed = 0.02;
float speeddiff = 0.02;
// http://lslwiki.net/lslwiki/wakka.php?wakka=llApplyImpulse
default
{
state_entry()
{
llSetTimerEvent(0.5); // generate a timer event every 1 second
}
timer()
{
vector ra = <llfrand(movebump),llfrand(movebump),llfrand(movebump)>;
//llOwnerSay("ball moving "+(string)ra);
//llOwnerSay("ball moving mag "+(string)llVecMag(ra));
vector vel = llGetVel();
float velmag = llVecMag(vel);
if ( (velmag>0) && (velmag>maxspeed) )
{
//llOwnerSay("oldvelmag = "+(string)llVecMag(vel));
// need to slow the ball
float magdev = maxspeed / velmag;
vel = vel * magdev;

llApplyImpulse(llGetMass()*vel,FALSE);
//llOwnerSay("newvelmag = "+(string)llVecMag(vel));
}
else if ((velmag==0) (velmag<minspeed)) ra="<llFrand(movebump),llFrand(movebump),llFrand(movebump)>;
//llOwnerSay("ball moving "+(string)ra);
//llOwnerSay("ball vel 0 move bump = "+(string)llVecMag(ra));
// give the ball a little wiggle
llPushObject(llGetKey(), ra, <0,0,0>, FALSE);
//llOwnerSay("push mag = "+(string)llVecMag(ra));
}
}
}