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));
}
}
}

Monday, June 30, 2008

Need Second Life volunteers for a quick photo

The Second Life pinball machine is coming along nicely. I'm over two weeks ahead on blog posts so you should be seeing slow, but significant progress over the coming weeks.

I'm getting to the point where I'm deciding on the art theme of the pinball machine and I've decided I would like to make it a "Second Life pinball machine". That means that I need to get some good in world photos that might work as the score board and table background. If you would be interested in being notified (and possibly immortalized on a pinball machine) when I would get everyone together, please send me an e-mail (wood@side8.com, SL:Wood Wheels) and I'll pick a time and a date for the shoot. Also if you have ideas for a place that might work well I'm open to suggestions. I'm thinking something on a hillside or pyramid shape will allow more people to fit in the sort of vertical space I'm needing.

Friday, June 27, 2008

Second Life Script - Number Texture Display #2

Once I had a working number I had to place a few of them in sequence and allow a master prim to control them.



I had to first have a way for the root prim (the scoreboard) to send a message to the individual numbers telling them to update their display. I did this with a link message.


integer MSG_SET_NUM = 141; // arbitray message number
link_message(integer from, integer msg_id, string str, key id)
{
if (msg_id == MSG_SET_NUM)
{
curval = (integer)str;
llOwnerSay("setting num to "+(string)curval);
doOffset();
}
}


Then in the root prim I had to find the individual link numbers of the linked numbers. This is done like this. You first iterate through the list of links and find the ones named 1,10,100,1000 and save their link number. An avatar siting on the item will have a key and will have the highes link number so you have to skip them.

integer thousands = -1;
integer hundreds = -1;
...
state_entry()
{
integer current_link_nr = llGetNumberOfPrims();
// Check if it's more than one
if (1 <>0)
{
if (llGetLinkName(current_link_nr)=="1")
{
llOwnerSay("found 1: "+(string)current_link_nr);
one = current_link_nr;
}
else if (llGetLinkName(current_link_nr)=="10")
{
ten = current_link_nr;
llOwnerSay("found 10: "+(string)current_link_nr);
}
....
current_link_nr--;
}
}
}


Then when the number is changed, you send get the value of the thousands, then hundreds, then tens, then ones and send each on to the various number displays.



integer MSG_SET_NUM = 141; // the same message id defined in the number
doSendNumbers()
{
integer itmp = curval;
if (itmp>10000)
itmp=9999;

integer i;
i = itmp / 1000;
llOwnerSay("thousands = "+(string)i);
if (thousand!=-1)
{
// send the value to the thousands number
llMessageLinked(thousand, MSG_SET_NUM, (string)i, NULL_KEY);
}
itmp = itmp - (i*1000);

i = itmp / 100;
llOwnerSay("hundreds = "+(string)i);
if (hundred!=-1)
{
llMessageLinked(hundred, MSG_SET_NUM, (string)i, NULL_KEY);
}
itmp = itmp - (i*100);
.....


I had one more method on the scoreboard. When an avatar touches the scoreboard I increment the value and send the value out to the various individual numbers.

touch_start(integer total_number)
{
curval++;
if (curval>9999)
curval = 0;

doSendNumbers();
}

Thursday, June 26, 2008

Second Life Script - Number Texture Display

I needed a number display system. I've seen these in other items, but as usual I'm willing to just figure it out and build my own.

I first created a simple 16x256 pixel image of all the numbers. I made 16 numbers because textures are all power of two stuff. I doubt I'll do any hexadecimal displays, but I had the extra room.


I then used hit and miss to figure out the offsets for the numbers. I found the change from one number to the next was about 0.6 then worked backwards and forwards until I had both edges and -0.47 and +0.47. To make this programticall, I used (0.47*2)/16 to get 0.05875 which I tried, then finally figured out that I needed to divide by 15 because one cell is not counted in the actual size. That gave me an offset of 0.62666666.

integer curval = 0;
doOffset()
{
float ftmp = -0.47+(curval*0.062666);
llOwnerSay("offset ="+(string)ftmp);
llOffsetTexture(ftmp,0.0,ALL_SIDES);
}

default
{
state_entry()
{
doOffset();
}

touch_start(integer total_number)
{
// u offsets
//0=-0.47
//1=-0.41
//2=-0.35
//3=-0.29 *
//4=-0.22
//5=-0.16
//6=-0.10
//7=-0.04 *
//8=0.03
//9=0.09
//A=0.15
//B=0.21
//C=0.28 *
//D=0.34
//E=0.41
//F=0.47
// 0.47*2/15 = 0.06266666
curval++;
if (curval>16)
curval = 0;
doOffset();
}
}

There is one glaring problem I noticed after I got the number image uploaded. I probably need one cell that is only transparent so I can turn of the number. I'll do this later with another image upload and use -1 to display the blank number, but then I will not be able to show a hexadecimal number! Oh well... I'll probably end up making the other columns numeric symbols like dollar, pound, comma, period and such.



Make sure you apply the number texture to only one face. The rest of the faces are completely transparent. A simple trick is to select the texture face on the build dialog, then click on the individual faces to apply the texture to only that face.

Wednesday, June 25, 2008

Second Life Physics Scripting - Pinball #4

I need to make the ball a little smarter when it got stuck on the lower slope. I got a little crazy and added something to try and keep it at a constant velocity of 0.42M (the size of the ball), since anything faster than this can cause it to move through a wall across a frame rate. This is the final script.

float movebump = 0.2;
float maxspeed = 0.42;
float speeddiff = 0.02;
// http://lslwiki.net/lslwiki/wakka.php?wakka=llApplyImpulse
default
{
state_entry()
{
llSetTimerEvent(1.0); // 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);

// check for anything above or below the maximum velocity
if ( (velmag>0) && ((velmag>maxspeed+speeddiff) || (velmag<maxspeed-speeddiff)))
{
//llOwnerSay("oldvelmag = "+(string)llVecMag(vel));
// need to slow or speed up the ball
float magdev = maxspeed / velmag;
vel = vel * magdev;

// take that new velocity and apply it to the ball
llApplyImpulse(llGetMass()*vel,FALSE);
//llOwnerSay("newvelmag = "+(string)llVecMag(vel));
}
else if (velmag==0)
{
// need to give it a random bump!
vector 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("ball vel "+(string)llGetVel());
// give the ball a little wiggle
//llPushObject(llGetKey(), ra, <0,0,0>, FALSE);
}
}

I think there are still problems with it, because it should have been a problem in that it would never slow down and move down the board in the way I "thought" I coded it, but it does work, but it also still gets stuck briefly. I had to add the check for 0 velocity at the end because it was getting divide by zero and that else (velmag==0) piece of code was all I really needed in the first place. I'll have to revisit it, but at least it doesn't get stuck indefinitely any longer, but it still gets stuck a lot more than I'm willing to live with.

Tuesday, June 24, 2008

Second Life Physics Scripting - Pinball #3

The ball kept getting stuck, and I was looking for clever ways to keep it from getting stuck. This is what I tried as a script within the ball itself.

float movemax = 0.002;

default
{
state_entry()
{
llSetTimerEvent(1.0); // generate a timer event every 1 second
}

timer()
{
vector ra = ;
//llOwnerSay("ball moving "+(string)ra);
//llOwnerSay("ball moving mag "+(string)llVecMag(ra));
// give the ball a little wiggle
llPushObject(llGetKey(), ra, <0,0,0>, FALSE);
}
}

A simple timer and an added small push no matter if it needs it or not. This worked well for the steeper grade, but it still gets stuck on the machine with the "curved" slope (two planes one steeper than the other). It keeps getting stuck in the angle between the two planes. I either need to check the velocity every second and give it a bigger push if it is lower than some amount, or place some sort of accelerator in the area of the change in angle. It'll probably need to be some sort of combo between the two because I think I'll need the accelerator to make the game more interesting since the ball seems to have lost a lot of momentum once it reaches the "curve".

Monday, June 23, 2008

Second Life Physics Scripting - Pinball #2

The problems I was seeing with missed collision detection were (I believe) mostly fixed. The velocity of an object cannot exceed it's size or it may travel farther than itself in a single frame tick.


float maxspeed = 0.42;
default
{
collision_start(integer total_number)
{
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);

// direction from wall to ball
vector pos3 = pos2-pos;

pos3.x = pos3.x*4;
integer ra = (integer) llFrand(1.0)-1;
pos3.y = pos3.y*ra;

/// check the magnitude of the speed versus the max speed
float mag = llVecMag(pos3);
if (mag>maxspeed)
{
// limit the speed to the max magnitude
float magdev = maxspeed / mag;
pos3 = pos3 * magdev;
}
llPushObject(llDetectedKey(0), pos3, <0,0,0>, FALSE);
}
}
}

The Lindens chose this method to increase performance. The alternative would be to check for collisions multiple times during each fram as something moves. It would be nice if there were one more flag where you could turn on a finer granularity on an object, say multiple checks based on it's size, but for now it will have to be a slower game if I'm going to continue making a pinball game.

I created this contraption to test my scripts for bounce through. The pink ball just bounces up into the first wall and goes through it if it is going too fast. It hits the top wall without losing the ball and I can rerun the test. I have been able to get away with a little larger value than the object size because there is some immedite friction before it reaches the wall, but that just means that it WILL still happen so I'll need to account for a ball off the table, but it should/better be infrequent.

Friday, June 20, 2008

Second Life Physics Scripting - Pinball

I have been continuing to learn Linden Scripting Langage (lsl) in Second Life. I was stuck for a while on a board game and 2d video game idea and decided to move on to something else. My latest round of work has been learning the physics engine. I've been doing this by building a pinball machine.



The basics of this are in the collision_start method in the backwall of the pinball machine. The ball is a simple sphere with physics turned on.

default
{
collision_start(integer total_number)
{
//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);

// subtract the two positions to find the direction from the wall to the ball
vector pos3 = pos2-pos;
//llOwnerSay("pos = "+(string)pos+" pos2="+(string)pos2);
//llOwnerSay("pos3 = "+(string)pos3);

// do some multiplication to get a big bounce
pos3.x = pos3.x*4;

// change the y direction in minute ways to create a random bounce
integer ra = (integer) llFrand(1.0)-1;
pos3.y = pos3.y*ra;

// push the ball in that direction
llPushObject(llDetectedKey(0), pos3, <0,0,0>, FALSE);
}
}
}

This failed miserably since the ball kept leaping off the table. After a lot of reading it turns out that the collision system in second life only does detection on each frame so if the ball is moving fast it will be across the wall between frames and you have to go hunting for the ball. I'll discuss solutions in upcoming posts.

Thursday, June 19, 2008

Second Life Land Grab - Bay City #2

I've been thinking more about the Bay City Land grab. It seems like the Lindens struck a nerve and people are really excited about this new land. To me, the difference is in the roads and always having an open side to your land. Up until now it seemed like you could get trapped very easily by a couple of bad neighbors unless you wanted to spend a lot on a big space and waste a lot of it.

Then I was wondering why the Lindens didn't think about this earlier? They've said their initial designs came directly from Snow Crash and as I remember, roads were an integral part of the VR system in the book, so why no roads until now? Seems odd to me, but at least they finally figured it out. Now all they need to do is replicate this land style a few hundred more times and get the prices out of the stratosphere!

For those starting new sims, know that open space is important to a community and greatly affects land prices.

Here is a screen shot of a map that shows how much of this brand new land is up for sale. All the prices are over $200,000L (about $100US) for 1024. Here is a link/slurl for Bay City Imaginario (http://slurl.com/secondlife/Bay%20City%20-%20Imaginario/28/70/45) so you can check out those prices for yourself.

Wednesday, June 18, 2008

Second Life Land Grab - Bay City

I finally decided to get a little piece of my own land in Second Life. I was searching around trying to find a good plot for some simple builds and a group I'm helping. I was watching auctions and searching around and noticed the new community of Bay City being auctioned off. People were paying more for 1024 meter ($190,000L = $700US) plots than they were paying for a whole island. I'm not sure that these people are completely sane, but the land they were buying was nice. It had a lot of open space and roads between plots so land was not sandwiched in between four different neighbors.

Try visiting that area now, it is mostly baren with most of those 1024M plots up for resale for around $280,000L=$1000US. Crazy.

I passed on Bay City then noticed that the Lindens are starting to build roads through some of the older sims. I found a spot that had the acquired land for roads next to it, but no road built yet. I bought a small 512M plot next to that. I paid about $11L/Sq meter. Land value is basically around $7L/Sq Meter unless it has some geography. Since it was going to be next to a road I figured it was worth a little more and it fit my needs. I don't like all the subdividing that is going on along the roads. People selling tiny plots next to the roads for pure advertising. I found one that does not have a lot of that going on.

Thursday, March 20, 2008

Still Learning Second Life Script

I'm still learning a lot about Second Life scripting even though I've been through a couple of rounds of what I would call significant digging. The latest learning as you might know from this blog was a Tic-Tac-Toe game I built. I've been working on another game, still in the rough draft phase trying to discern what might be possible and found a page of examples on the lsl wiki.

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

I was digging through these and found a full example of building a Tic-Tac-Toe game. Had I known this existed, I probably wouldn't have read it before hand anyways because I sometimes like to figure stuff out then compare to how others solve the same problems.

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

As I expected, we went about it in different ways, but I do think that both solutions are fairly equal. I did force people to sit at the table and after I was done thought it would be better to have people simply click to choose sides. He did the later and it is a lot easier to setup than the code I did to watch for people that sit down. Good learning experience though.

One of the best parts of this example is the discussion on Version Control. (http://www.lslwiki.net/lslwiki/wakka.php?wakka=ExampleTicTacToeVersionControl) This is something I struggled with early on and he has a very elegant solution to keep all the scripts in the root prim and copy them out to the child prims when you type "/1 listen" into chat. I spent a lot of time reading this section of the example and will probably be implementing something similar for a future game. A full example isn't given and it glosses over some of the aspects that make this a general use solution so this is definitely going to be home grown.

I haven't really gotten past that page yet and I'll have to spend some time to finish it. Very worthwhile reading.

Monday, March 3, 2008

My Web Comic Turned 50

A little over a year since I first turned over my daily Farside calendar and started drawing my own webcomic (in about 30 seconds as the drawing is very simple), I've reached 50 posts and decided to take a break. It was fun and I still have some more I could post, but it doesn't feel healthy to put my mind in such a negative light and keep producing these. I recently read C.S. Lewis' Screwtape Letters and his prologue mention his unwillingness to continue writing more letters (even given high reader/publisher demand) because of the negative attitude this produced within himself. This hit home. Not to compare myself to C.S. Lewis as he is a huge figure, it just reminded me of my own feelings on the subject of writing in a negative light. Hopefully I'll be inspired to write a more positive webcomic, but then I'll probably have an even smaller audience than I've garnered with this one. Of course the final webcomic is one that mocks myself so it seemed an appropriate place to take a break.

http://facebigelow.blogspot.com