7 August 2013
6 November 2012
REPEAT
loops. Please see the latest code.27 February 2012
25 February 2012
llAbs
for some normalization.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.
Logo TURTLE | Logo TURTLE REPEAT |
---|---|
The video on the left illustrates the gyration on the axes for roll, pitch and yaw. The video on the right illustrates the REPEAT
command.
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.
* 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)
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.
This list will be updated as we extend the LOGO parser to include and process the rest of the LOGO semantics.
listen
times out.Take an expression such as:
REPEAT 2 [ AB REPEAT 3 [ GL ] ] REPEAT 2 [ TL ]
The expected output is:
AB GL GL GL AB GL GL GL TL TL
We do that using the following algorithm:
REPEAT
:REPEAT
and 2
from the input.[ AB REPEAT 3 [ GL ] ]
and delete it from the input.[
and ]
).0
:AB REPEAT 3 [ GL ]
.AB
, print the token.GL
, print the token.TL
, print the token.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.
////////////////////////////////////////////////////////// // Wizardry and Steamworks (c) 2012, GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html // // for legal details, rights of fair usage and // // the disclaimer and warranty conditions. // ////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////// // ABOUT // ////////////////////////////////////////////////////////// // This is an implementation of LOGO in 3D. // // The original developement page can be found at: // // http://grimore.org/secondlife:logo_turtle // ////////////////////////////////////////////////////////// // EXAMPLES: // ////////////////////////////////////////////////////////// // LOGOParse("REPEAT 4 [ FO 1 YL 90 ]"); // // - draws a square. // // LOGOParse("REPEAT 3 [ FO 1 YL 120 ]"); // // - draws an isosceles triangle // ////////////////////////////////////////////////////////// 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(llEuler2Rot(<-1,0,0> * DEG_TO_RAD) * llGetRot()); rollLeft(--degrees); } rollRight(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<1,0,0> * DEG_TO_RAD) * llGetRot()); rollRight(--degrees); } pitchUp(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,-1,0> * DEG_TO_RAD) * llGetRot()); pitchUp(--degrees); } pitchDown(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,1,0> * DEG_TO_RAD) * llGetRot()); pitchDown(--degrees); } yawLeft(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,0,1> * DEG_TO_RAD) * llGetRot()); yawLeft(--degrees); } yawRight(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,0,-1> * DEG_TO_RAD) * llGetRot()); yawRight(--degrees); } list bs = []; string inner(string in) { if(llStringLength(in) == 0) return ""; string c = llGetSubString(in, 0, 0); in = llDeleteSubString(in, 0, 0); if(c == "[") { bs += "["; jump next; } if(c == "]") { if(llGetListLength(bs) == 0) { return ""; } string peek = llList2String(bs, llGetListLength(bs)-1); if(peek == "[") { bs = llDeleteSubList(bs, llGetListLength(bs)-1, llGetListLength(bs)-1); jump next; } } @next; return c + inner(in); } LOGOParse(string input) { list split = llParseString2List(input, [" "], [""]); do { string in = llList2String(split, 0); if(in == "REPEAT") { // Get the number of times to repeat. integer times = llList2Integer(split, 1); // Delete the "REPEAT" command and number of times. split = llDeleteSubList(split, 0, 0); split = llDeleteSubList(split, 0, 0); // Clean the sub-expression of brackets. string sub = llDumpList2String(split, " "); sub = llDeleteSubString(sub, 0, 0); sub = llDeleteSubString(sub, llStringLength(sub)-1, llStringLength(sub)-1); sub = inner(sub); // And now delete the sub-expression from the input // leaving the rest of the input in split for to // be processed by the current recursive branch. split = llDeleteSubList(split, 0, 0); list delete = llParseString2List(sub, [" "], [""]); do { split = llDeleteSubList(split, 0, 0); delete = llDeleteSubList(delete, 0, 0); } while(llGetListLength(delete) != 0); split = llDeleteSubList(split, 0, 0); // Here we go. For the number of times we have to repeat // the sub-expression, go ahead and parse it that number // of times. do { LOGOParse(sub); } while(--times); // Don't pop off the next REPEAT, we're done here, loop again! jump continue; } if(in == "FO") { front(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("FO"); } if(in == "BA") { back(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("BA"); } if(in == "LE") { left(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("LE"); } if(in == "RI") { right(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("RI"); } if(in == "UP") { up(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("UP"); } if(in == "DO") { down(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("DO"); } if(in == "RR") { rollRight(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("RR"); } if(in == "RL") { rollLeft(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("RL"); } if(in == "PU") { pitchUp(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("PU"); } if(in == "PD") { pitchDown(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("PD"); } if(in == "YR") { yawRight(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("YR"); } if(in == "YL") { yawLeft(llList2Integer(split, 1)); /* DEBUG */// llOwnerSay("YL"); } split = llDeleteSubList(split, 0, 0); @continue; } while(split); } default { state_entry() { llListen(0, "", llGetOwner(), ""); } listen(integer channel, string name, key id, string message) { // Enqueue commands by injecting them into the recursive parser. LOGOParse(message); // Enqueue the commands on the call stack. } on_rez(integer num) { llResetScript(); } }
This is kept here as a reference for the old version of the code.
/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, 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(llEuler2Rot(<-1,0,0> * DEG_TO_RAD) * llGetRot()); rollLeft(--degrees); } rollRight(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<1,0,0> * DEG_TO_RAD) * llGetRot()); rollRight(--degrees); } pitchUp(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,-1,0> * DEG_TO_RAD) * llGetRot()); pitchUp(--degrees); } pitchDown(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,1,0> * DEG_TO_RAD) * llGetRot()); pitchDown(--degrees); } yawLeft(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,0,1> * DEG_TO_RAD) * llGetRot()); yawLeft(--degrees); } yawRight(integer degrees) { if(degrees == 0) return; llSetRot(llEuler2Rot(<0,0,-1> * DEG_TO_RAD) * llGetRot()); 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); } }
We have switched the Wizardry and Steamworks hideout to full C#-LSL hybrid scripting. Use this script if you are on OpenSim and prefer C#. This variant in C# does not support REPEAT
.
//c# /////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, 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); }