LScript v2.0 Release Notes

LScript v2.0 Release Notes




  A new function called filefind() has been added that takes
  a file name (sans path information) and scans through the
  directory paths in the application in order to locate the
  file.  The path to the file is returned.
  
        if((where = filefind("lw.cfg")) != nil)
        {
            ...

  This function uses those paths that are available to
  the getdir() function in order to find the file.


  A new function called filecrc() calculates the CRC-32 value
  of the contents of a file, and returns this integer value.


  LScript has a new array type known as associative arrays.
  Associative arrays are indexed using string values instead
  of integer indices.  Associative arrays are powerful
  mechanisms that can be used to simulate user-defined
  structures, such as those found in C or Pascal.

  In associative arrays, the index value used (the character
  string) is the symbol to which attributes are associated.
  The attributes are contained in the arrays that are indexed
  using the symbol.  Yes, it all sounds very confusing.  It
  should be clearer, however, in practice.

        fruit_color["apple"]    = "red";
        fruit_color["banana"]   = "yellow";
        ...
        fruit_shape["apple"]    = "round";
        fruit_shape["banana"]   = "curved";
        ...
        fruit_weight["apple"]   = 128;      // grams
        fruit_weight["banana"]  = 117.3;    // grams
        ...

  As you can see from this short example, the name of the
  instance of the structure is the index symbol ("apple",
  "banana", etc.).  The individual members of each
  structure is actually the name of each array ("fruit_color",
  "fruit_shape", etc.).

  Associative arrays can be created/initialized with the
  new associative initializer operator '$'.  These
  new operators are used in a similar fashion to the
  initializer block operators ('@').

        normal_array = @1,2,3,4@;
        assoc_array  = $ .. $;

  In order to initialize an associative array, however,
  you must use pairs of elements.  The first part of
  each pair must be a character string that is the
  associative symbol for the data found in the second part.

        fruit_color  = $ "banana", "yellow", "apple", "red" $;


  A new form of conditional expressions is available in the
  language.  Traditional conditional expressions use the if()
  test construct in a pre-condition position:

        if(<expression>>)
        {
            ...
        }

  The new in-line conditionals allow single statements to be
  conditionally evaluated:

        x = 1 if y == 15 && cos(4.35);
        ...
        error("Division by zero!") when z == 0;
        ...
        x = 1 unless y == 10 || z == 2;
        ...
        break if x == 5;
        ...
        return 5 if !upper_limit;
        ...


  There is a new implicit 'this' container that holds the
  results of the evaluation of the last expression.  This
  system is not yet fully implemented, but it allows for
  not having to handle immediate values using intermediate
  variables:

        ...
        while(!file.eof())
        {
            file.read();  // the line goes into 'this'

            // does the line begin with "PUB"
            // followed by a C++ comment?

            if(s~^PUB.*\/\/.*~)
                info();   // display it to the user...
        }
        ...


  Boolean tests are now "short circuited", allowing expressions
  like this to be used:

        ...
        file = "C:\\CONFIG.SYS";
        f = File(file,"r") || error("Cannot open file '",file,"'");
        ...


  The LScript Generic system now supports Layout's new
  CommandSequence system, and currently has over 160 new commands
  to facilitate this.  CS commands are accessed by name, and they
  exist in LScript as functions.

        @version 2.0

        generic
        {
            AddNull();
        }

  When a CS command requires parameters, they should be provided
  as though you were calling a UDF.


  The LScript pre-preprocessor now supports a new pragma
  directive called "script".   This directive is an identifier
  that is intended to aid LightWave's LCore system to identify
  a script being added as a plug-in.

        @script modeler
        @script displace
        @script generic
        @script motion
        @script image
        @script replace
        @script master
        @script shader
        @script channel


  LScript now has in-language support for regular and
  search-and-replace expressions.  Regular expressions are powerful
  pattern matching functions that have their own special meta-
  character langauge.  This pattern-matching system originated
  on the UNIX operating system, and has permeated a great many
  places in the software world.  There is a large body of reference
  available for regular expressions, so I will not provide a
  detailed explanation of them here.  Rather, I will tell you
  how to employ them in LScript.

  To perform pattern matching using regular expression in
  LScript, you need to use the new "s~~" operator.  This operator
  instructs LScript to use the regular expression provided to
  compare against a character string to see if the pattern
  exists.

        // see if the string begins with a
        // capital "B", followed anyplace
        // by two consecutive "o"'s

        if("Bob Hood" == s~^B.*oo~)
        {
            ...

  Regular expressions can also be used to perform search-and-replace
  operations on character strings.  This mechanism is accessed
  using the "r~~~" operator.  This operator instructs LScript to
  match the pattern provided, and to replace it with the character(s)
  provided.  In order to access the search-and-replace mechanism,
  the new search-and-replace assignment operator "~=" is used:

        // a sed-like search-and-replace using the s/r operator

        if((input = File("nfa.c","r")) == nil)
            return;

        if((output = File("nfa.bak","w")) == nil)
            return;

        while(!input.eof())
        {
            line = input.read();

            // replace any "PRIVATE" strings found
            // at the start of the line (^) with
            // the characters "static "

            line ~= r~^PRIVATE~static ~;
            output.writeln(line);
        }

        input.close();
        output.close();

  Regular expression patterns can be assigned to variables, and
  those variables used in their place:

        // a sed-like search and replace using the s/r operator

        if((input = File("nfa.c","r")) == nil)
            return;

        if((output = File("nfa.bak","w")) == nil)
            return;

        sr = r~^PRIVATE~static ~;

        while(!input.eof())
        {
            line = input.read();

            line ~= sr;
            output.writeln(line);
        }

        input.close();
        output.close();

  LScript can also construct regular expressions from character
  strings using the new regexp() function.  This can be useful
  for allowing users to enter regular expression patterns to
  be used:

        // display lines in a file that begin with the letters "PUB"

        // compile a regular expression to be used
        expr = regexp("^PUB");

        // regexp() returns 'nil' if the expression fails to compile
        if(expr == nil)
            return;

        if((file = File("nfa.c","r")) == nil)
            return;

        while(!file.eof())
        {
            line = file.read();

            if(line == expr)
                info(line);
        }

  Regular expressions also recognize the 'this' container

        ...
        while(!file.eof())
        {
            file.read();

            if(expr)
                info();
        }
        ...


  Multidimensional arrays no longer exist as entities within
  LScript.  All arrays are now strictly linear.  However,
  arrays can be assigned to specific elements of other arrays,
  allowing multi-dimensional arrays to be constructed by the
  script as it executes.  Such user-defined multidimensional
  arrays are as dynamic as their linear counterparts, and
  elements can be added to any point at will.

        // generate a multidimensional array "on-the-fly"
        a[3,5,2] = 0;

  For efficiency, multi-dimensional arrays created in this
  fashion are not "robust."  For example, the above array
  creation generates a three-element array, whose third
  element is an array of five elements, whose fifth element
  is an array of two elements, whose second element has a
  value of zero (0).  Put graphically:

        a[1] -> nil
        a[2] -> nil
        a[3] -> [1] -> nil
                [2] -> nil
                [3] -> nil
                [4] -> nil
                [5] -> [1] -> nil
                       [2] -> 0

  To create "robust" multidimensional arrays (where all
  elements are fully populated), it must be formally
  declared as all multidimensional arrays were required
  to be in LScript v1.x:

        var     a[3][5][2];

  Mult-dimensional arrays can be constructed by piecing
  together linear arrays:

        a[5] = "Bob";
        b[3] = a;
        b[3,5,2] = 'a';     // the string "Bob" now becomes "Bab"

  Because arrays can now be members of other arrays,
  arrays-as-elements can now be passed to user-defined
  functions:

            ...
            // sub-array passing to functions

            a[5] = "Bob";
            b[2] = 99.9;
            b[3] = a;
            c[8] = b;

            info(c);
            info(c[8]);
            info(c[8,3]);
            info(c[8,3,5]);

            test(c);
            test(c[8]);
            test(c[8,3]);
            test(c[8,3,5]);
        }

        test: a
        {
            info(a);
        }


  Images loaded into Layout (through the Image Editor) can now
  be displayed using the ctlimage() function.  Image names
  prefaced by a dollar sign character ($) will be resolved by
  LScript using any loaded image files instead of trying to
  locate an image file on disc.  Names following the dollar
  sign metacharacter should exactly match that of the named
  displayed for the image by Layout.

        ...
        ctlimage("$ph_019.tga",0,0);
        ...

  Images referenced in this fashion are ignored when the script
  is compiled for run-time execution.  This means that the image
  referenced must be available from Layout when the compiled
  script is executed.


  The ctlimage() function has three new (optional) parameters
  for use in scaling images displayed with it.  The fifth
  parameter (following the optional transparency vector)
  specifies the scaling factor or size of the image's width.
  Parameter six specifies the scaling factor or size of the
  image's height.  The final parameter (7) is a boolean flag
  that instructs LScript to preserve the images aspect ratio.
  By default, the aspect ratio is not preserved.

  The numeric values that are provided for width and height
  scaling are interpreted by LScript by their type.  If you
  provide an integer value for either component, then that
  value will be considered an absolute width (in pixels) of
  the resulting image.  On ther other hand, if you pass a
  number (floating point) in that position, then LScript
  will consider it a percentage factor (where 1.0 equals 100%)
  that will be applied to the images original width or
  height to determine the final pixel width.  Because this
  mechanism is not designed to enlarge images, anything that
  causes the resulting width or height to exceed the image's
  original dimensions will be clamped to the image's
  original dimension.

        ...
        // display the image at (0,0), 40% of its original
        // width and 30% of its original height

        ctlimage("$ph_019.tga",0,0,,.4,.3);

        ...
        // display the image at (0,0), 100 pixels wide
        // and 100 pixels tall, preserving its aspect
        // ratio (which means that it will probably not
        // be exactly 100x100 when displayed)

        ctlimage("$ph_019.tga",0,0,,100,100);

        ...
        // display the image at (0,0) in its original
        // (unaltered) form

        ctlimage("$ph_019.tga",0,0);


  User-defined functions can now be called recursively.  This
  capability only extends to those functions that you yourself
  define within your script.  Pre-defined functions--those
  known to LScript, like main() or options()--cannot be called
  recursively.

  Recursion is a powerful feature, yet it can also be tricky and
  dangerous.  Unless you understand what you are doing, you can
  send LScript into an infinite loop (which will eventually lead
  to a crash of the application).  LScript attempts to support
  endless levels of recursion, however, other practical limits
  (such as memory) restrict the depth.

  Before using recursion, be sure you know what you're doing.
  Here is a Modeler LScript that uses recursion to traverse
  the entire root directory hierarchy to count the number of
  directories that exist on the drive:

        @warnings

        output;
        count;

        main
        {
            count = 0;

            chdir("\\");    // start at the root directory
            do_dir();

            info(count," directories processed!");
        }

        // a "depth-first" recursive directory traversal

        do_dir
        {
            if((directories = matchdirs(".","*.*")) == nil)
                return;

            len = directories.size();

            for(x = 1;x <= len;x++)
            {
                chdir(directories[x]);
                ++count;

                do_dir();       // <-- recursion

                chdir("..");
            }
        }


  The LScript language now has a new iterator construct called
  foreach().  This construct is intended to simplify the process
  of iterating over a linear series of values.  It takes as
  parameters a variable to be used to contain each value, and
  an expression that will evaluate into some data type capable
  of iteration.

  The foreach() iterator accepts the following data types for
  iteration:

  • integer
  • number
  • File Object Agent
  • Array (including Modeler LScript's points[] and polygons[]
    automatic arrays, and Associative arrays)
... editbegin(); foreach(x,polygons) info(x); editend(); ... -------------------- ... foreach(x,10 - 5) info(x); ... -------------------- ... f = File("c:\\config.sys","r"); foreach(x,f) info(x); ... -------------------- ... obj = getfirstitem(MESH); foreach(x,obj) info(x.name); ... -------------------- ... a = @1,2,3@; foreach(x,a) info(x); ...

  An Image Object Agent has been added to the list of Layout objects
  to which LScript provides an interface.  getfirstitem() will now
  accept either IMAGE or "IMAGE", returning the first image file
  in Layout's Image list, or 'nil' if none are loaded.  Each Image
  Object Agent contains the following attributes:

    name        the name as it appears on the interface
    isColor     a boolean indicating a color or b&w image
    width       the image's width in pixels
    height      the image's height in pixels

  Additionally, each Image Object Agent recognizes the following
  methods:

    <string>            filename(<frame>)
    <grey>              luma(<col>,<row>)
    <r>,<g>,<b>         rgb(<col>,<row>)
    <ImageObject>       next(<void>)

  The following methods are only available to LScript/PT scripts:

    <void>              needAA(<void>);
    <number>            lumaSpot(<col>,<row>,<size>,<blend>);
    <num>,<num>,<num>   rgbSpot(<col>,<row>,<size>,<blend>);


        generic
        {
            image = getfirstitem("IMAGE") || error("No images loaded!");

            foreach(i,image)
                info(i.name,": ",.i.width,",",i.height);
        }


  Two new functions are now available in Layout LScript, called
  loadimage() and clearimage().  These functions will,
  respectively, load a new image file into Layouts image list,
  and clear that image (unload it).
  
        generic
        {
            image = loadimage("f:/lw/cdrom/FilmImage.tga") || error("Can't load image!");
            info(image.width,",",image.height);
            clearimage(image);
        }


  Expressions in LScript can now be chained together, when
  appropriate.

        ...  
        // access a File Object Agent from an array element
        v[3] = nil;
        v[2] = File("f:/dirs.txt","r");
        info(v[2].read().isStr());
        ...  

        ...  
        // get the first line of a text file in one line
        line = File("f:/dirs.txt","r").read();
        info(line);
        ...  

        ...  
        // access a vector
        v[2] = <1,2,3>;
        info(v[2].x);
        ...  

  Expression chaining cannot be used on the left of an
  assignment, because expressions can only exist on the
  right of an assignment statement.  For instance, the
  following would be invalid, and generates an error:

        ...  
        v[2] = <1,2,3>;
        v[2].x = 0;
        ...  


  The Associative array type been added to the data types accepted
  by the foreach() iterator.  Key values are processed in the order
  in which they are found in the internal hash tree.


  Reentrancy has been a problem for Layout LScript in certain
  situations in previous versions.  As a specific example, if
  a single plug-in is managing two or more objects (instances)
  that have dependencies on each other, then it is possible
  that the plug-in's process() function can be called when
  the plug-in is already in the process() function.  LScript
  has been unable to handle this situation because it uses
  global variables (for historical reasons).

  An instance stack has been implemented in Layout LScript v2.0
  in order to allow this reentrancy to take place in scripts.  It
  allows two or more objects to have interdependencies when they
  each have the same type of LScript plug-in applied.  Here's an
  example of an interdependency that would crash previous versions
  of Layout LScript (based on an example provided by Alexandre Bon).

    1. Add three Nulls to a scene, named "A", "B", and "C".
    2. Give Null "A" motion.
    3. Apply the Item Motion script below to Null "C", and
       tell it to watch "B".
    4. Apply this same script to Null "B" and tell it to
       watch to "A".
    5. Run the animation.

        @version 2.0
        @script motion

        parent;

        process: ma, frame, time
        {
            if(!parent) return;

            newpos = parent.param(POSITION,time);
            newpos.z -= 2;

            ma.set(POSITION,newpos);
        }

        options
        {
            if(!reqbegin("Follow The Leader"))
                return;

            c1 = ctlallitems("Select parent",parent);

            if(reqpost())
                parent = getvalue(c1);

            reqend();
        }


  Improvements in LScript's virtual machine code for v2.0
  make it possible now to use object-based array references
  on the left of an assignment:

        v[1] = <1,2,3>;
        v[1].x = 10;

        . . .

        t = 1;
        obj[t] = ObjectMan("Cow.lwo");
        obj[t].surfaces[t] += "_Bob";


  The Layout LScript compiler will automatically select the
  plug-in architecture for you if it detects the new "@script"
  pragma in the selected source script.


  A new debug() command has been added to the LScript
  function toolset.  This command takes no arguments, and, when
  it is encountered, it will attempt to establish a session
  with the new LSIDE LScript Debugger.

  If the LScript Debugger is already running, and is idle,
  a debug session for the current script will activate.

  If the LScript Debugger is not already running, LScript will
  attempt to locate and execute the LScript Debugger.  Once it
  is active, a debug session will begin.  LScript will look for
  the LScript Debugger binary in the same directory as the
  LightWave binaries and libraries.


  Layout CS functions that expect an item id as their argument
  (such as SelectItem() or GoalItem()) will accept a varying
  combination of arguments to aid you in designating the needed
  Layout object.
  
    - Integer values can be specified that are formatted as
      they are in the LightWave scene file.  These values
      are hexidecimal, so some bit twiddling will be necessary
      in order to place values in the correct locations of
      the 32-bit integer:

            // select the second Mesh object

            SelectItem((1 << 28) | 1);

    - A string value can be provided that should evaluate
      to the name of an object in the current scene.

    - An object class can be specified (MESH, LIGHT, CAMERA),
      followed by an integer that specifies the linear offset
      (1-based) of the object in the list of objects of that
      class.

            // select the third Light in the scene

            SelectItem(LIGHT,3);


- The following Layout CS functions accept an optional character
  string to name the new object.  If a name is not provided, Layout
  will post a requester to the user.

            AddNull
            AddBone
            AddChildBone
            AddDistantLight
            AddPointLight
            AddSpotlight
            AddLinearLight
            AddAreaLight
            ReplaceWithObject
            ReplaceWithNull
            LoadObject


  Owing to changes in the underlying plug-in API for LightWave [6],
  the following LScript functions, introduced in LScript v1.x, and
  relating to surfaces, have been removed:

            _getRawSurf()
            _setRawSurf()


  A new preprocessor pragma directive has been added to control the
  equality testing of floating-point values.  The 'fpdepth' pragma
  specifies the number of digits to the right of the decimal that
  are to be considered significant when floating point values
  (numbers, vectors, etc.) are compared for equality.  The pragma
  setting has domain over the entire run-time operation of the
  script.

        @fpdepth 3


  A new object masking mechanism has been added to the language.
  This mechanism allows objects of integral data types that support
  it to perform a "masking" operation on themselves.  The exact
  result of this masking function will vary by the data type.

    - Floating-point values (numbers, vectors) will
      use the masking value to render only that number
      of digits to the right of the decimal as significant.

    - Character strings will use the masking value to
      return that number of characters from the left
      (equivalent to strleft()).

    - All other data types will evaluate to themselves
      (i.e., masking is not applicable).

  In order to mask an object, you follow the object operator
  ('.') with an integer value.  For instance, the following
  code will print the value "1.34":

        ...
        bob = 1.34398545;
        info(bob.2);
        ...

  Masking operators can only be used on variables.

  Expression chaining can also be used:

        ...
        bob = 1.34398545;
        info(bob.2.asStr().isStr());        // prints 1
        ...


  A pair of new LScript plug-ins have been added to provide
  scripting capabilities to the Layout Channel Filter plug-in
  architecture.  'ls-cf.p' and 'lsrt-cf.p' are the text and
  run-time support plug-ins for this architecture.

  The process() UDF receives three arguments.  The first
  is a Channel Access Object Agent, the second the current
  frame number, and the third is the current time index.

        ...
        process: ca, frame, time
        {
        }
        ...

  The Channel Access Object Agent exports one data member,
  'name', which represents the displayable name of the channel
  to which the script is applied.  In addition, two methods are
  available, get() and set(), for retrieving and setting
  (respectively) the channel's data.  This data is a floating-
  point value.

  The get() method requires a time index.  The set() method
  accepts the new value for the channel at the time index
  provided to the process() UDF.


  Item Motion LScripts can now optionally accept the Mesh Object
  Agent to which they belong in their create() function:

        @script motion
        ...
        create: obj
        {
            info(obj.filename);
        }
        ...

  This argument is optional and may be left off of the UDF's
  definition.


  LScript Object Agents now provide an interface to their
  individual channels.  New methods and Object types allow
  you to iterate through, and interact with, an object's
  envelope channels.

  The new firstChannel() method will return the first
  channel associated with an object.  Like the firstChild()
  method, this method must be used as the preface of an
  iteration through all channels.  The nextChannel() method
  returns the next channel in the list of channels for an
  object.  Both methods will return 'nil' if there are no
  (further) channels available.

        light = getfirstitem(LIGHT);

        c = light.firstChannel();
        while(c)
        {
            ...
            c = light.nextChannel();
        }


  A new Channel Object Agent has been added to the LScript
  OA toolset.  The new Object Agent provides a structured
  interface to individual channel functionality.  The new
  OA methods firstChannel() and nextChannel() return this
  new OA type.  In some circumstances, a Channel Object
  Agent will be provided to a UDF (as in the case of the
  create() UDF for the Channel Filter LScript).

  As of this release, the Channel Object Agent exports the
  following data members:

        name        the name of the channel as it appears
                    in the Layout interface (i.e., Graph
                    Editor)

        type        the type of the channel.  this can be
                    one of the following: NUMBER, DISTANCE,
                    PERCENT, ANGLE

  Two methods are available:

        value()     this method causes the channel to be
                    evaluated at the specified time.  the
                    return values is a floating-point number
                    that should be interpreted based upon
                    the channel's type.

        event()     this method is used to activate a callback
                    function that will be invoked whenever an
                    event occurs on the channel.  such events
                    could be the creation or deletion of
                    keyframes in the envelope, or a change in
                    the a keyframe's value.

  The following Channel Filter script illustrates the usage
  of this new Object Agent:

        @warnings
        @script channel
        @version 2.0

        create: channel
        {
            light = getfirstitem(LIGHT);

            c = light.firstChannel();
            while(c)
            {
                last if c.name == "Position.X";
                c = light.nextChannel();
            }

            // keep abreast of changes in this
            // channel...

            c.event("lightEvent");
        }

        process: ca, frame, time
        {
            ca.set(0.0);
        }

        lightEvent: channel,        // Channel Object Agent
                    event           // event code
        {
            // something happened to the Light's
            // "Position.X" channel

            info(event);
        }


  C-like character escape sequences for tabs and newlines
  ('\t' and '\n') are now supported in strings.


  A conversion function has been added that takes a character
  value and returns its integer equivalent.  This is handy
  for comparing non-printing characters:

        if(ascii(line[x]) == '\t')
            ...


  A conversion method called 'asAsc' has been added to the expression
  chain for converting character values:

        if(line[x].asAsc() == '\t')
            ...


  The Scene Object Agent now has two iteration methods that are
  used to cycle through the objects in the current scene that
  are selected.  The methods firstSelect() and nextSelect()
  are used in an fashion identical to other iteration methods,
  like firstChild() and nextChild() in the Layout Object Agent.

        scene = getfirstitem(SCENE);

        obj = scene.firstSelect();
        while(obj)
        {
            ...
            obj = scene.nextSelect();
        }


  The Scene Object Agent now has a data member called
  'currenttime' that holds the time index that is currently
  selected in the Layout interface.


  The Scene Object Agent now has a data member called
  'recursiondepth' that holds the integer recursion depth
  used for rendering.


  The Layout Object Agent (the Object Agent returned for any
  scene object) sports four new data members to its interface.
  Each is a boolean flag that indicates the state of the
  indicated condition:

        selected
        childrenvisible
        channelsvisible
        locked


  All Layout Object Agents now export a data member called 'genus'
  that holds the type of the object.  It holds one of MESH, LIGHT,
  CAMERA, BONE, SCENE, or CHANNEL.


  The LScript pre-processor has a new testable condition called
  'platform'.  This value is one of INTEL, ALPHA, SGI, MACINTOSH, or
  SUN.  It can be tested with either the '==' or '!=' equality
  operators.

    ...
    @if platform == ALPHA
        info("Alpha!");
    @end
    @if platform == INTEL
        info("Intel!");
    @end
    ...


  The LScript pre-processor has a new testable condition called
  'compiler'.  This value is an either/or condition.  Either it exists,
  or it doesn't.  It can be tested for existence:

    ...
    @if compiler
        ...
    @end
    ...

  Or, it can be tested for non-existence:

    ...
    @if !compiler
        ...
    @end
    ...

  This condition is useful for hiding elements from the script
  compiler that are invalid in the run-time environment:

    ...
    @if !compiler
        debug();
    @end
    ...


  The AddPlugins() CS function accepts an optional string argument
  that should be the filename of a plug-in to be added.


  Layout Object Agents have a new data member called 'id' that holds
  the integer identifier of the Layout object for which they serve
  as proxy.  This integer identifier is the same one used by Layout
  to uniquely identify objects in the scene file.

    generic
    {
        obj = getfirstitem(LIGHT);
        info(hex(obj.id));         // prints "0x20000000"
    }


  Point and polygon selections in Modeler using POINTNDX, POLYNDX,
  POLYID and POINTID are now exact.  Element positions are no longer
  a factor.  This means that now you can discretely select a single
  point, even if it occupies the same space as one or more other
  points.


  The implicit 'this' container will capture and convert into an array
  any multiple-value function returns.  Such returns are made by functions
  like parse():

        ...
        parse(" ","Today is the day");
        info(this[3]);      // prints "the"
        ...

  The values contained in the this[] array are only valid, however, until
  the next expression is evaluated.  At that time, the array will be
  reclaimed by LScript's garbage collection mechanism, and a new value
  (possibly even a new array) will be put into its place.


  The regular expression assignment operator (~=) will support both types of
  regular expression (Search, and Search-And-Replace).  In the case of Search
  regular expressions, the variable on the left of the assignment is not
  modified; rather, this mechanism enables the 'this' container to be
  populated with the tokens that match the individual patterns of the
  expression (see the next entry for an example).


  The patterns matched within a Search regular expression are implicitly
  converted into a lexeme array held by the 'this' container when those
  patterns are enclosed within parentheses.  For example, the following
  invocation of the regular expression engine will not generate a lexeme
  array in the 'this' container because no parenthetical groupings are
  employed around the search patterns:

        ...
        command ~= s~[a-zA-Z]+ .+~;

        info(this[2]);      // <-- generates an error, "undeclared array"
        ...

  However, wrapping the patterns within parentheses will cause a lexeme
  array to be generated and assigned to 'this':

        ...
        command ~= s~([a-zA-Z]+) (.+)~;

        info(this[2]);      // <-- if the pattern matched, no error
        ...

  If the pattern fails to match on the provided string value, then the
  'this' container will not contain an array.  You can code for this
  condition by performing a logical test of the success of the pattern
  match before accessing 'this' as an array:

        ...
        if(command == s~([a-zA-Z]+) (.+)~)
            info(this[2]);      // <-- safe to access this[2]
        ...


  Arrays can now be the source of an associative assignment.

        ...
        colors = @"Red","Green","Blue"@;
        (red,green) = colors;   // assigns "Red" to red, and so on
        ...

  This allows the contents of the 'this' container to be accessed
  before the contents are overwritten.

        ...
        if(command == s~([a-zA-Z]+) (.+)~)
        {
            (item1,item2) = this;
            ...


  Several new Requester functions have been added to support
  user-drawing features in the panel.  The drawing functions
  can only be called from within the script's redraw callback.

        reqredraw("callback")

            This function establishes a callback function
            within the script that will be activated whenever
            the panel itself is redrawn by the Panels system.

        drawpixel(<color>,x,y)

            Draws a pixel on the panel at the specified
            point using the specified color

        drawline(<color>,x1,y1,x2,y2)

            Draws a line on the panel between the specified
            points using the specified color

        drawbox(<color>,x,y,w,h)

            Draws a box filled with the specified color having
            the specified width and height positioned at the
            specified point

        drawborder(x,y,w,h)

            Draws an unfilled, 3D box outline at the specified
            point using the specified width and height.  If
            height is 0, then a horizontal divider line will
            be drawn.

        drawtext("text",<color>,x,y)

            Draws the provided text using the specified color
            at the specified point


  The rotation for an object's pivot point can now be accessed
  using the PIVOTROT constant to the param() method, or by
  calling the new getPivotRotation() method.


  Two new floating-point buffers are avaialble to LScript Image
  Filter plug-ins.  Known as MOTIONX and MOTIONY, these buffers
  are the vector-based motion values for objects in the scene.


  Frame start/stop/step settings have been broken into separate
  categories for preview and render.  The previewstart, previewend,
  and previewstep Scene Object Agent data members represent the
  values used by the LightWave preview system (and entered into
  the corresponding edit fields on the user interface).  The
  renderstart, renderend, and renderstep values represent those
  used when performing an actual render of the scene.
  
  The render settings are synonymous with the deprecated framestart,
  frameend, and framestep data members.  These
  data members will be removed in a later release of LScript
  in favor of renderstart, renderend, and renderstep.  You are
  encouraged to replace any references to framestart, frameend,
  and framestep in your existing LScript v2.0 scripts, and to
  avoid their use in any future scripts.