LOGO Turtle

From Second Life Wiki
Jump to: navigation, search
KBnote.png Note: Due to licensing issues, all contributions to this wiki have stopped and the articles that we posted are just being maintained. This is one of the projects that has gone further and for updates you are cordially invited to the project page on our wiki.

ChangeLog

6 November 2012

  • Reworked the parser and added deeply-nested REPEAT loops. Please see the latest code.
  • Parser fix.
  • Gyration fix for yaw-left, yaw-right.

27 February 2012

25 February 2012

  • Cleaned up more, fixed all the translations except the testing ones.
  • Added llAbs for some normalization.
  • Renamed FR -> FO to conform to our specification.
  • Added some more friendly output in case a notecard was not found.
  • Video is not private anymore o.O (wtf).

Introduction

The LOGO language is a small programming language, originally developed by NASA and meant for educational purposes. The language stems basically from the LISP-category of programming languages and thus sharing the same ancestor with the Linden Scripting Language. This allows us to create a (sweet) fully-recursive parser implementation based on movements. Reading the various references, it seems that the turtle was meant to move in a two-dimensional space, on the XY plane and had no spatial component.

Due to the necessity of educators trying to explain rotations and translations in Second Life, as well as the concepts behind them, we give LOGO a new dimension by implementing a third dimension.

YouTube Video: Rotations

The video above illustrates the gyration on the axes for roll, pitch and yaw.

YouTube Video: REPEAT

The video above illustrates the REPEAT command.

Translations and Rotations

Because of the third dimension, we extend the movement possibilities by using aircraft terminology to represent rotations around axes. We simplify the language and create a dialect of acronyms where every movement or rotation is given by the two first letters of the word describing the translation or rotation.

Overview

The Front direction represents a translation in the positive sense of the x-axis, the Left translation represents a translation in the positive sense of the y-axis and an upward movement represents a translation along the positive sense of the z-axis (3-right hand, finger rule).

Key

* FO means front - along the positive sense of the x-axis
* BA means back - along the negative sense of the x-axis
* UP means up - along the positive sense of the z-axis
* DO means down - along the negative sense of the z-axis.
* LE means left - along the positive sense of the y-axis.
* RI means right - along the negative sense of the y-axis.
* RL means roll left - rotation in angles around the x-axis (in the positive sense of the y-axis)
* RR means roll right - rotation in angles around the x-axis (in the negative sense of the y-axis)
* PU means pitch up - rotation in angles around the y-axis (in the positive sense of the z-axis)
* PD means pitch down - rotation in angles around the y-axis (in the negative sense of the z-axis)
* YL means yaw left - rotation in angles around the z-axis (in the positive sense of the y-axis)
* YR means yaw right - rotation in angles around the z-axis (in the negative sense of the y-axis)

Real-Time Parser

Similar to our XML Animation Overrider based on StAX-technology, the LOGO Turtle uses a real-time parser that reads and extracts the commands from the local chat and enqueues them to the stack of already existing commands. Since we are dealing with stacks and queues (please note, that in this case we use them interchangeably: we pop elements off the stack to determine the next command and we enqueue a list of commands at the bottom of the stack), it is easy to write a recursive descent parser that traverses a stack by consuming a list.

Furthermore, all the movements are expressed recursively, in a step-like fashion without using physics. The step-like movement behavior of the LOGO Turtle, contrary to Wanderer, is an intentional feature because we are aiming for compatibility with step-by-step motors and other gyration equipment.

Since listen events are themselves enqueued on the call stack, every time a new set of commands is issued on the local chat, we enqueue the parser on the run-time call-stack by calling the same recursive function again on the new list of commands:

parse(commands)

which, albeit needing appropriate security restrictions (call-stack overflow being a concern for a sufficiently many commands posted on the local chat), we believe it is a very elegant solution. The reason for that, being no doubt, the beautiful LISP-like behavior of both LOGO and LSL.

Features

This list will be updated as we extend the LOGO parser to include and process the rest of the LOGO semantics.

  • Movements on all the 6-senses of the 3-axes.
  • Backward and forward rotations around all the 3-axes.
  • Streamed commands piped directly into the program (by using call-stack enqueueing).
  • Notecard loading support for an initial set of commands.

Limitations

  • The system assumes that the notecard has one blank line at the end.
  • The system assumes that a command and a parameter is always separated by space.

Usage

Simply create a blank primitive and drop the script inside. Then follow the key-overview sections above and issue commands or write them up in the notecard.

Some example input could be:

LE 5
FR 5
RI 5
BA 5

which is also equivalent to:

LE 5 FR 5 RI 5 BA 5

would make the primitive move on the perimeter of a square.

Future Endeavors

Very soon, we're not going to be in Kansas anymore... Stay tuned... We're taking things to the next level. ;-)

Code

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
key nQuery = NULL_KEY;
integer nLine = 0;
string commands = "";
//pragma inline
string nName = "LOGO";
 
front(integer step) {
    if(step == 0) return;
    llSetPos(llGetPos() + <1,0,0>*llGetRootRotation());
    front(--step);
}
 
back(integer step) {
    if(step == 0) return;
    llSetPos(llGetPos() + <-1,0,0>*llGetRootRotation());
    back(--step);
}
 
left(integer step) {
    if(step == 0) return;
    llSetPos(llGetPos() + <0,1,0>*llGetRootRotation());
    front(--step);
}
 
right(integer step) {
    if(step == 0) return;
    llSetPos(llGetPos() + <0,-1,0>*llGetRootRotation());
    back(--step);
}
 
up(integer step) {
    if(step == 0) return;
    llSetPos(llGetPos() + <0,0,1>*llGetRootRotation());
    up(--step);
}
 
down(integer step) {
    if(step == 0) return;
    llSetPos(llGetPos() + <0,0,-1>*llGetRootRotation());
    down(--step);
}
 
rollLeft(integer degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() / llEuler2Rot(<1,0,0> * DEG_TO_RAD));
    rollLeft(--degrees);
}
 
rollRight(integer degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() * llEuler2Rot(<1,0,0> * DEG_TO_RAD));
    rollRight(--degrees);
}
 
pitchUp(integer degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() / llEuler2Rot(<0,1,0> * DEG_TO_RAD));
    pitchUp(--degrees);
}
 
pitchDown(integer degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() * llEuler2Rot(<0,1,0> * DEG_TO_RAD));
    pitchDown(--degrees);
}
 
yawLeft(integer degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() / llEuler2Rot(<0,0,1> * DEG_TO_RAD));
    yawLeft(--degrees);
}
 
yawRight(integer degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() * llEuler2Rot(<0,0,1> * DEG_TO_RAD));
    yawRight(--degrees);
}
 
parse(list command) {
    if(!llGetListLength(command)) return; // Empty list.
 
    // DEBUG: Queue display on every command.
    //llOwnerSay("List: " + llDumpList2String(command, " "));
 
    integer steps = llAbs(llList2Integer(command,1));
    if(llList2String(command,0) == "FO") {
        front(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "BA") {
        back(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "LE") {
        left(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "RI") {
        right(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "UP") {
        up(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "DO") {
        down(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "RR") {
        rollRight(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "RL") {
        rollLeft(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "PU") {
        pitchUp(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "PD") {
        pitchDown(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "YR") {
        yawRight(steps);
        jump next; // break;
    }
    if(llList2String(command,0) == "YL") {
        yawLeft(steps);
        jump next; // break;
    }
@next;
    parse(command = llDeleteSubList(command, 0, 0));
}
 
default
{
    state_entry() {
        llListen(0, "", llGetOwner(), "");
        integer itra;
        for(itra=0, commands="", nLine=0; itra<llGetInventoryNumber(INVENTORY_NOTECARD); ++itra) {
            if(llGetInventoryName(INVENTORY_NOTECARD, itra) == nName)
                jump found_notecard;
        }
        llInstantMessage(llGetOwner(), "Failed to find notecard. Listening just to local chat...");
        return;
@found_notecard;
        nQuery = llGetNotecardLine(nName, nLine);
    }
    listen(integer channel, string name, key id, string message) {
        // Enqueue commands by injecting them into the recursive parser.
        parse(llParseString2List(message, [" "], [])); // Enqueue the commands on the call stack.
    }
 
    touch_start(integer total_number) {
        parse(llParseString2List(commands, [" "], []));    
    }
 
    changed(integer change) {
        if(change & CHANGED_INVENTORY) {
            llResetScript();
        }
    }
 
    dataserver(key id, string data) {
        if(id != nQuery) return;
        if(data == EOF) {
            llOwnerSay("Instructions loaded...");
            return;
        }
        if(data == "") jump next_line;
        commands += data + " ";
@next_line;
        nQuery = llGetNotecardLine(nName, ++nLine);
    }
}

C#-LSL Code

We have switched the Wizardry and Steamworks hideout to full C# scripting. Use this script if you are on OpenSim and prefer C#.

//c#
//////////////////////////////////////////////////////////
// [K] Komarov - 2011, License: GPLv3                   //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
LSL_Types.LSLString nQuery = "";
LSL_Types.LSLInteger nLine = 0;
 
string commands = "";
//pragma inline
string nName = "LOGO";
 
LSL_Types.Vector3 _px = new LSL_Types.Vector3(1,0,0);
LSL_Types.Vector3 _nx = new LSL_Types.Vector3(-1,0,0);
LSL_Types.Vector3 _py = new LSL_Types.Vector3(0,1,0);
LSL_Types.Vector3 _ny = new LSL_Types.Vector3(0,-1,0);
LSL_Types.Vector3 _pz = new LSL_Types.Vector3(0,0,1);
LSL_Types.Vector3 _nz = new LSL_Types.Vector3(0,0,-1);
 
LSL_Types.list spc = new LSL_Types.list(" ");
LSL_Types.list emp = new LSL_Types.list("");
 
public void default_event_state_entry()
{
    // Listen for input.
    llListen(0, "", llGetOwner(), "");
    LSL_Types.LSLInteger itra = llGetInventoryNumber(INVENTORY_NOTECARD)-1;
    do {
        if(llGetInventoryName(INVENTORY_NOTECARD, itra) == nName) {
            nQuery = llGetNotecardLine(nName, nLine);
            break;
        }
    } while(--itra>=0);
}
 
public void default_event_listen(LSL_Types.LSLInteger channelIn, LSL_Types.LSLString name, LSL_Types.LSLString id, LSL_Types.LSLString message) {
    parse(llParseString2List(message, spc, emp));
}
 
private void parse(LSL_Types.list commands) {
    if(!llGetListLength(commands)) return; // Empty list.
 
    string command = llList2String(commands, 0);
    LSL_Types.LSLInteger steps = llAbs(llList2Integer(commands,1));
    switch(command) {
        case "FR":
            front(steps);
            break;
        case "BA":
            back(steps);
            break;
        case "LE":
            left(steps);
            break;
        case "RI":
            right(steps);
            break;
        case "UP":
            up(steps);
            break;
        case "DO":
            down(steps);
            break;
        case "RR":
            rollRight(steps);
            break;
        case "RL":
            rollLeft(steps);
            break;
        case "PU":
            pitchUp(steps);
            break;
        case "PD":
            pitchDown(steps);
            break;
        case "YR":
            yawRight(steps);
            break;
        case "YL":
            yawLeft(steps);
            break;
        default:
            break;
    }
    parse(commands = llDeleteSubList(commands, 0, 0));
}
 
private void front(LSL_Types.LSLInteger step) {
    if(step == 0) return;
    llSetPos(llGetPos() + _px*llGetRootRotation());
    front(--step);
}
 
private void back(LSL_Types.LSLInteger step) {
    if(step == 0) return;
    llSetPos(llGetPos() + _nx*llGetRootRotation());
    back(--step);
}
 
private void left(LSL_Types.LSLInteger step) {
    if(step == 0) return;
    llSetPos(llGetPos() + _py*llGetRootRotation());
    front(--step);
}
 
private void right(LSL_Types.LSLInteger step) {
    if(step == 0) return;
    llSetPos(llGetPos() + _ny*llGetRootRotation());
    back(--step);
}
 
private void up(LSL_Types.LSLInteger step) {
    if(step == 0) return;
    llSetPos(llGetPos() + _pz*llGetRootRotation());
    up(--step);
}
 
private void down(LSL_Types.LSLInteger step) {
    if(step == 0) return;
    llSetPos(llGetPos() + _nz*llGetRootRotation());
    down(--step);
}
 
private void rollLeft(LSL_Types.LSLInteger degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() / llEuler2Rot(_px * DEG_TO_RAD));
    rollLeft(--degrees);
}
 
private void rollRight(LSL_Types.LSLInteger degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() * llEuler2Rot(_px * DEG_TO_RAD));
    rollRight(--degrees);
}
 
private void pitchUp(LSL_Types.LSLInteger degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() / llEuler2Rot(_py * DEG_TO_RAD));
    pitchUp(--degrees);
}
 
private void pitchDown(LSL_Types.LSLInteger degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() * llEuler2Rot(_py * DEG_TO_RAD));
    pitchDown(--degrees);
}
 
private void yawLeft(LSL_Types.LSLInteger degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() / llEuler2Rot(_pz * DEG_TO_RAD));
    yawLeft(--degrees);
}
 
private void yawRight(LSL_Types.LSLInteger degrees) {
    if(degrees == 0) return;
    llSetRot(llGetRot() * llEuler2Rot(_pz * DEG_TO_RAD));
    yawRight(--degrees);
}