A short tutorial how to build linked objects, which are physical and flexible at the same time, with a fish movement script as an example
First create a box A. Edit it, click Object->More and select the Physical checkbox. Then create a box B and select the Features->Flexible Path checkbox. Set 1 for wind for testing. Now first select box B, then ctrl-click box A to select both and click Link in Tools menu. This makes box A the root prim in the link set. Now the whole link set is physical, but box B is still flexible.
Finally you can add the script below to the link set for a fish movement. Edit the objects by checking the "Edit linked parts" checkbox to make box A invisible, apply texture to box B and rotate it for the right bending effect, change the flexible parameters and move box A within the link set for more realistic movement (somewhere inside the fish).
// bending fish
// 2007 Copyright by Shine Renoir (fb@frank-buss.de)
//
// The center position is stored on rez.
// maximum swim radius from last center
float radius = 3.0;
// maximum swim distance for swimming up or down from last center
float height = 1.0;
// delay in seconds for next movement
float delay = 4.0;
// internal channel for communication
integer CHANNEL = -87;
// last center position
vector center;
float randBetween(float min, float max)
{
return llFrand(max - min) + min;
}
init()
{
llOwnerSay("start swimming");
llListen(CHANNEL, "", llGetOwner(), "");
llSetTimerEvent(0.0);
llSetStatus(STATUS_ROTATE_X, FALSE);
llSetStatus(STATUS_ROTATE_Y, FALSE);
float t = llSqrt(2.0) / 2.0;
llSetRot(<0, 0, 0, 0>);
center = llGetPos();
llSetBuoyancy(1.0);
llSetTimerEvent(delay);
}
default
{
touch_start(integer num)
{
llDialog(llGetOwner(), "Click 'stop', then move the fish to a new center and then click 'start'. Change parameters with the other buttons", ["radius", "height", "delay", "stop", "start"], -33);
}
state_entry()
{
init();
}
on_rez(integer start_num)
{
init();
}
timer()
{
// get current position
vector pos = llGetPos();
// calculate random next position
vector dest = pos;
dest.x += randBetween(-radius, radius);
dest.y += randBetween(-radius, radius);
dest.z += randBetween(-radius, height);
// move to center, if outside radius
integer i;
for (i = 0; i < 3; i++) {
if (llVecMag(dest - center) > radius) {
dest = (dest - pos) / 2.0 + pos;
}
}
if (dest.z < center.z - height) {
dest.z += 0.5;
}
if (dest.z > center.z + height) {
dest.z -= 0.5;
}
// fallback: if other objects pushes the fish, move back to center
if (llVecMag(dest - center) > radius) {
dest = center;
}
// calculate new rotation and move to target
vector delta = pos - dest;
float angle = llAtan2(delta.y, delta.x) + PI / 2.0;
rotation rot = llEuler2Rot(<0, 0, angle>);
llRotLookAt(rot, 1.0, 1.0);
llMoveToTarget(dest, 2);
}
listen(integer channel, string name, key id, string message)
{
if (message == "stop") {
llOwnerSay("stop swimming");
llSetTimerEvent(0.0);
llStopMoveToTarget();
} else if (message == "start") {
init();
} else if (message == "radius") {
llDialog(llGetOwner(), "Change radius in meters from center of the fish. Current radius: " + (string) radius, ["radius +1", "radius -1", "radius +5", "radius -5"], -33);
} else if (message == "radius +1") {
radius += 1.0;
llOwnerSay("new radius: " + (string) radius);
} else if (message == "radius -1") {
radius -= 1.0;
if (radius < 1.0) {
radius = 1.0;
}
llOwnerSay("new radius: " + (string) radius);
} else if (message == "radius +5") {
radius += 5.0;
llOwnerSay("new radius: " + (string) radius);
} else if (message == "radius -5") {
radius -= 5.0;
if (radius < 1.0) {
radius = 1.0;
}
llOwnerSay("new radius: " + (string) radius);
} else if (message == "height") {
llDialog(llGetOwner(), "Change moving height in meters. Current height: " + (string) height, ["height +1", "height -1", "height +5", "height -5"], -33);
} else if (message == "height +1") {
height += 1.0;
llOwnerSay("new height: " + (string) height);
} else if (message == "height -1") {
height -= 1.0;
if (height < 1.0) {
height = 1.0;
}
llOwnerSay("new height: " + (string) height);
} else if (message == "height +5") {
height += 5.0;
llOwnerSay("new height: " + (string) height);
} else if (message == "radius -5") {
height -= 5.0;
if (height < 1.0) {
height = 1.0;
}
llOwnerSay("new height: " + (string) height);
} else if (message == "delay") {
llDialog(llGetOwner(), "Change max delay in seconds. Current delay: " + (string) delay, ["delay +1", "delay -1"], -33);
} else if (message == "delay +1") {
delay += 1.0;
llOwnerSay("new delay: " + (string) delay);
} else if (message == "delay -1") {
delay -= 1.0;
if (delay < 1.0) {
delay = 1.0;
}
llOwnerSay("new delay: " + (string) delay);
}
}
}