# LScript v1.4 Release Notes (final...really...)

### (updated 02.07.99)

### ENHANCEMENTS & BEHAVIOURAL CHANGES

      Modeler LScript can now use LW Panels if it is installed.  If it is
      not, then LScript will revert to the built-in Modeler requester
      interface, substituting as best it can equivalent controls for those
      provided by Panels.

      Use of Panels in Modeler LScript is not automatic.  The reqbegin()
      function has been altered to accomodate this.  If you employ reqbegin()
      in its original form, then LScript will use the built-in Modeler
      requester.

      However, you can now provide an optional second boolean parameter
      that instructs LScript to use the LW Panels plug-in instead if it
      is available.  A boolean return value is now provided by reqbegin()
      to indicate the presence (true) or absence (false) of the plug-in.

                . . .
                if(!reqbegin("Panels Test",true))
                    return;
                . . .
      The asStr() method has had some formatting enhancements made for
      floating-point numbers, and vector data types now adhere to any
      floating-point formatting specifications.
      A new requester control function has been added.  This function,
      ctlimage(), allow you to embed Targa Image Files (type 2 and 10,
      top-down or bottom-up raster) anywhere on your Panels-based
      requester panel.  ctlimage() takes one required and three optional
      parameters:  the image file name, the image's X (horizontal) offset,
      the image's Y (vertical) offset, and a color transparency mask -- as
      a single integer or a vector -- for simple color filtering (which
      can, in some cases, greatly increase display speed).

      You can also use the ctlposition() function to position your
      image anywhere on your panel.  You can use ctlposition() with
      a ctlimage() control without triggering LScript's control
      position release logic.

            . . .
            if(!reqbegin("Image Test",true))
                return;

            reqsize(500,300);

            gravity = .9;
            ground = 0.0;
            startRange = 30;
            axis = 2;

            // load a Targa image that is 500x50
            ctlimage("f:/tmp/mental.tga");  // default to: (0,0)

            c0 = ctlsep();
            c1 = ctlnumber("Initial velocity (m/s)",gravity);
            c2 = ctlnumber("Ground",ground);
            c3 = ctlinteger("Disperal Range",startRange);
            c4 = ctlpopup("How 'bout a pop-up?",2,@"Item 1","Item 2","Item 3"@);
            c5 = ctlchoice("Test",axis,@"X","Y","Z"@);

            ctlposition(c0,0,50);   // separator at the bottom of the image

            ctlposition(c1,10,110); // put all the other controls below the
            ctlposition(c2,10,130); // image area...
            ctlposition(c3,10,150);
            ctlposition(c4,10,170);
            ctlposition(c5,10,190);
            . . .
      Due to a design flaw in the plug-in API, plug-ins that load data in from
      a scene file can exceed the boundaries of their saved data, even to the
      point of reaching the end of the scene file.  Unfortunately, Layout
      mistakenly shares the scene file pointer with the plug-in (instead of
      correctly making a separate copy), so when a plug-in that has read beyond
      its data returns control to Layout, the scene can either become corrupted,
      or Layout may simply load nothing more (because its scene file pointer has
      hit EOF).

      The LScript Layout read() function has been bullet-proofed against this.
      It will now detect the end of a plug-in's data chunk (indicated in the
      scene file by the marker "EndPlugin"), and will set the EOF flag for
      any remaining data read()'s, thus preventing the script from reading any
      scene data that it does not own.
      The return count and types for the LScript/PT illuminate() function
      have been changed.  In versions prior, six floating-point values are
      returned, three for direction, and three for color.  Now, three values
      are turned:  ray result (boolean; 'true' if light illuminates position,
      'false' otherwise), direction (vector), and color (vector).
      Both selpolygon() and selpoint() were designed to throw exceptions if they
      detected non-id elements in provided arrays.  This rather uptight behaviour
      has been altered so that both functions will now simply not look any
      further for id elements once they encounter a first non-id element.
      Those identifiers used for variables and function names can now be
      "constructed" at run-time using a new language extension.  This extension
      is triggered by using a "tick" mark or reverse apostrophe (`) found on the
      same key as the tilde.

      Starting with a base identifier, you add constructors to the end,
      preceding each with the "tick" mark.  For example, the following
      code fragment will assign the integer value 15 to the identifier
      "Bob36Hood":

             . . .
             x = 36;
             y = "Hood";
             Bob`x`y = 15;
             . . .

      When this code is executed, 'x' will contain 36, 'y' will hold "Hood",
      'Bob' will be nil (ultimately, we are not assigning to 'Bob'), and
      the new variable 'Bob36Hood' will be 15.

      Pseudo-arrays can be constructed by using this run-time identifier
      construction.  However, this should only be used for low-count
      situations; otherwise, you should employ LScript's integral array
      support:

            . . .
            for(x = 1;x <= 10;x++)
                value`x = x * x;
            . . .

      You can also use this mechanism to build function names (for
      invocation, NOT for definition):

            . . .
            reqbegin("Testing Identifier Construction");
            c1 = ctlchoice("Which action?",1,
                           @"Action1","Action2","Action3"@);

            if(!reqpost()) return;

            x = getvalue(c1);
            reqend();

            func`x();  // evaluates to 'func1', 'func2' or 'func3'
            . . .
      The random() function has been enhanced to generate floating-point random
      values if floating-point values are passed as arguments
      Tabs have been added to the requester control repertoire.  The are created
      using the ctltab() function.  This function takes a series of character
      strings that betitle each tab:

           . . .
           c3 = ctltab("Page 1","Page 2","Page 3");
           . . .

      Like ctlimage(), tabs can be positioned with ctlposition() without
      triggering LScript's control position release logic.

      Currently, only one tab control is allowed on the panel.
      Controls can be "placed" on tab pages using the ctlpage() command.
      This command takes a page number which corresponds to a defined
      tab, and one or more control identifiers.  When a control is placed
      on a tab page, it will only appear on the tab page when that tab is
      active.  If a control is not associated with a tab page, then it
      will remain visible on the requester panel at all times:

           . . .
           ctlpage(1,c4,c5);
           ctlpage(2,c6);
           ctlpage(3,c7,c8,c9);
           . . .
      getfirstitem() now returns a 'nil' value for objects identified by
      name that cannot be located.  In previous versions, a error message
      was printed to the user.
      Primitive-creation functions (makebox(), makeball(), maketesball(),
      makecone(), and makedisc()) will now create their components using
      default settings when no parameters are provided.
      Support for the Diffuse Shading (DIFFSHADE) and Specular Shading
      (SPECSHADE) Image Filter buffers has been added to LS/IF.
      The flags() function in LS/IF is now optional.  If omitted, LS/IF
      will automatically detect buffer accesses (through calls to
      bufferline() and floatline()) and ensure that accessed buffers are
      available when the process() function is called.
      The caching mechanism of LS/IF is now copy-on-write.
      LS/IF now sports a refresh() function that will discard any cache()'d
      image data and refresh from the original image data.
      A "flow" pragma has been added to the pre-processor.  When used, a
      limited tracing of the script flow will be generated to the file
      designated.  This pragma is not supported in the run-time environment
      (and should be turned off before distributing the script!).

            @flow "f:/test.flow"
      Modest speed improvements have been realized in the memory management.
      LS/IF is now completely cache-based.  As a result, the cache() command
      no longer performs any useful function, and is obsolete.  The bufferline()
      and floatline() commands are still functional, and are still the only
      means of accessing non-RGBA channels.  The getpixel() and putpixel()
      functions are now the preferred means of manipulating RGBA pixel data.
      Two hook functions, _getRawSurf() and _setRawSurf(), have been added
      to LScript Modeler.  These functions return and set the raw surface
      attribute chunk for a surface assigned to an object.  These functions
      are not intended for direct use by script writers, but rather have
      been added to allow an Object Agent to be written that will manage
      surface settings.
      A requester function has been added to allow controls to be grouped
      together around a "leader" control.  When grouped using the ctlgroup()
      function, all subordinate controls are then positioned *relative* to the
      group leader.  The first argument is the control id of the group leader,
      while all subsequent arguments are the controls that belong to the group.
      Groupings should be established PRIOR to setting control positions.

      For instance, this function "attaches" a separator to a tab control:

           . . .
           c0 = ctltab("Tab1","Tab2","Tab3");
           c1 = ctlsep();

           ctlgroup(c0,c1);        // <-- c0 is group leader

           ctlposition(c1,0,24);   // <-- relative to c0
           ctlposition(c0,0,10);   // <-- group "leader" -- c1 will follow
           . . .

      Groupings can also be nested:

           . . .
           if(!reqbegin("Panels Test",true))
               return;

           c0 = ctlcheckbox("Testing Number 1",true);
           c1 = ctlcheckbox("Testing Number 2",false);
           c2 = ctlcheckbox("Testing Number 3",true);
           c3 = ctlcheckbox("Testing Number 4",false);

           ctlgroup(c0,c1,c2);
           ctlgroup(c2,c3);

           ctlposition(c0,10,10);
           ctlposition(c1,0,20);   // <-- relative to c0
           ctlposition(c2,0,60);   // <-- relative to c0
           ctlposition(c3,0,20);   // <-- relative to c2
           . . .
      Because of the way the angle() function performs it calculations, returned
      angles, although accurate, would sometimes not match expectations.  With
      any angle measurement, there are always two possible values, the sum of
      which total to 360 degrees.  The angle() calculation logic will
      occasionally return one value when the script writer is expecting the
      other remainder.

      To avoid such confusion, the angle() function has been altered to always
      return the smaller of the two angle measurements -- which means that no
      returned angle will be greater than 180 degrees.  As before, you can get
      the inverse (or remainder) angle by subtracting the returned angle from
      360 degrees.
      A new feature has been added to the LScript requester mechanism.  Using
      a new function, ctlactive(), one or more requester controls can be
      activated and deactivated based upon the state of another control.
      This mechanism is by far the most complex of those yet created for the
      LScript requester.  It has a parameter structure much like ctlgroup(),
      however each call to ctlactive() must be companioned by an LScript
      callback UDF.

      The function parameters consist of the "state" control identifier as the
      first parameter (much like the group "leader" control for ctlgroup()):

        . . .
        c0 = ctlcheckbox("Testing Number 1",true);
        c1 = ctlinteger("Disperal Range",x);
        . . .
        ctlactive(c0,...       // 'c0' is the "state" control
        . . .

      The second parameter to ctlactive() is a character string that identifies
      a UDF in the current script:

        . . .
        ctlactive(c0,"toggleOn",...
        . . .

      All remaining parameters to ctlactive() are the identifiers of those controls
      whose state will be effected:

        . . .
        ctlactive(c0,"toggleOn",c1);
        . . .

      Active callbacks must accept a single argument that will hold the value of
      the "state" control at the time its state changed:

            . . .
            ctlactive(c0,"toggleOn",c1);
        . . .
        toggleOn: value
        {
            . . .
        }

      Callback UDFs must return a boolean true or false to indicate the active
      state of all governed controls.  A boolean true will cause governed controls
      to be active, while a false value will render them inactive.

      It is the script's responsibility to evalute the data passed to the
      callback UDF to determine the appropriate return value.  For instance,
      in the above code snippets, the "state" control is a checkbox.
      Therefore, the value passed to our callback UDF toggleOn() will be either
      true or false--the values that can be contained by the checkbox control.
      So, if we wish our governed controls to be active when the checkbox is
      "true", we would simply return the value passed to us:

        . . .
        toggleOn: value
        {
            return(value);
        }
        . . .

      However, if our "state" control were, for instance, a choice, we
      might want to activate our governed controls only when choice #2
      was selected:

            c0 = ctlchoice("Test",2,@"X","Y","Z"@);
            c1 = ctlcheckbox("Testing Number 1",true);
            c2 = ctlinteger("Disperal Range",x);
            . . .
            ctlactive(c0,"on2",c1,c2);
        . . .
        on2: value
        {
            return(value == 2);
        }

      "State" controls can be associated with up to ten (10) callback functions.
      This can allow you to perform more specific activations on controls.  For
      instance, the following code snippet will activate control 'c1' when the
      controller's integer value is greater than 10, and will activate control
      'c2' when the controller's integer value is less than, or equal to, 10:

            . . .
            x = 15;
            c0 = ctlinteger("Disperal Range",x);
            c1 = ctlcheckbox("Testing Number 1",true);
            c2 = ctlchoice("Test",2,@"X","Y","Z"@);
            . . .
            ctlactive(c0,"over10",c1);
            ctlactive(c0,"under10",c2);
        . . .
        over10: value
        {
            return(value > 10);     // turns 'c1' on or off
        }

        under10: value
        {
            return(value <= 10);    // turns 'c2' on or off
        }

      Unlike ctlgroup(), "state" controllers CANNOT be cascaded--i.e.,
      you cannot include a "state" identifier in the governed parameter
      list for another.
      Layout LScript has a new function called effectedby().  This function is
      used to inform Layout about the objects in the current scene to which
      your script has dependencies.  This function is LScript's interface to the
      plug-in API's useItems system.

      effectedby() can be called at anytime in your script, and accepts one or
      more character strings that represent the names of objects in the current
      scene whose parameters effect the script.  These names can also be
      passed to effectedby() through an array reference.

      This function has no actual impact on the execution of your script.
      Rather, it has impact on *when* your script is executed.  Layout will use
      the information provided by effectedby() to determine when your script
      (i.e., the LScript plug-in) should be executed in response to some change
      in the indicated objects.

        obj;

        create
        {
            obj = getfirstitem("MasterNull");

            // make sure process() is called
            // whenever "MasterNull" changes

            if(obj)
                effectedby("MasterNull");
        }

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

            v = obj.param(POSITION,time);
            ma.set(POSITION,v + 1);

            v = obj.param(ROTATION,time);
            ma.set(ROTATION,v);

            v = obj.param(SCALING,time);
            ma.set(SCALING,v);
        }

      The effectedby() function can also be called in your options() function
      in response to some change to the operation of the script made by the
      user.  For instance, you can allow the user to specify which object(s)
      will effect the script:

        obj;

        create
        {
            obj = nil;
        }

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

        options
        {
            if(!reqbegin("Parent"))
                return;

            c1 = ctlallitems("Select master object",obj);

            if(reqpost())
            {
                object = getvalue(c1);

                if(object && (object.name != obj.name))
                {
                    obj = object;
                    effectedby(obj.name);  // new dependencies!
                }
            }

            reqend();
        }
      A new function, normalize(), has been added to the global toolset.  This
      function takes a vector, and returns a normalized version of that vector:

        . . .
        vec = normalize(vec);
        . . .
      The ctlstring() and ctlfilename() requester functions now take an optional
      third argument that indicates the width of the input field.  This argument
      will be ignored if you are are not using the Panels plug-in to construct
      your requester.

        . . .
        test = "There!";
        c1 = ctlstring("Hey!",test,30);
        . . .
      Another new requester function has been added to the toolbox to allow
      controls to be visible or invisible based upon some condition.  Its calling
      parameters and operational requirements are identical to that of ctlactive():

        . . .
            c0 = ctlchoice("Test",2,@"Checkbox","Integer"@);
            c1 = ctlcheckbox("Testing Number 1",true);
            c2 = ctlinteger("Disperal Range",x);
            . . .
            ctlvisible(c0,"showCheckBox",c1);
            ctlvisible(c0,"showInteger",c2);
        . . .
        showCheckbox: value
        {
            return(value == 1);
        }

        showInteger: value
        {
            return(value == 2);
        }
        . . .

      A pleasing and professional-looking effect can be achieved by *stacking*
      controls that are conditionally visible, allowing them all to occupy the
      same location/space on the panel. In this way, the control itself appears
      to alter each time the condition changes:

            . . .
            c0 = ctlchoice("Test",2,@"X","Y","Z"@);
            c1 = ctlinteger("Disperal Range",x);
            c2 = ctlcheckbox("Testing Number 1",true);
            c3 = ctlpopup("How 'bout a pop-up?",2,@"Item 1","Item 2","Item 3"@);

            ctlvisible(c0,"vis1",c1);
            ctlvisible(c0,"vis2",c2);
            ctlvisible(c0,"vis3",c3);

            ctlposition(c0,10,10);

            // stack visiblity-controlled controls atop each other
            ctlposition(c1,0,30);
            ctlposition(c2,0,30);
            ctlposition(c3,0,30);
        . . .
        vis1: value
        { return(value == 1); }

        vis2: value
        { return(value == 2); }

        vis3: value
        { return(value == 3); }
        . . .
      The internal LScript memory manager now performs full-scale garbage
      collection of unused or temporary memory.  In previous releases of
      LScript, only character strings were reclaimed by the memory manager
      as a script executed.  Now, all LScript data types are reclaimed,
      preventing long-running scripts from consuming memory unnecessarily.
      This reclamation process has added overhead to the basic execution
      of LScript, which will unavoidably translate into longer execution
      times for all scripts.
      ctlalign() allows controls to be aligned with a master control using
      incremental offsets for the horizontal and vertical values.  The first
      parameter is the lead control whose position will be used as the
      beginning offet.  Next, come the X and Y offset increments as integers
      that will be applied to successive controls.  All remaining parameters
      are the controls that will be aligned relative to the master control
      and the increasing offsets.

        . . .
        c0 = ctlchoice("Test",2,@"X","Y","Z"@);
        c1 = ctlinteger("Disperal Range",x);
        c2 = ctlcheckbox("Testing Number 1",true);
        c3 = ctlpopup("How 'bout a pop-up?",2,@"Item 1","Item 2","Item 3"@);

        ctlalign(c0,0,20,c1,c2,c3); // align c1/c2/c3 at an X offset of 0, and
                                    // 20 successive units away from c0
        ctlposition(c0,10,10);
        . . .

      Offset values can also be negative.

        . . .
        ctlalign(c0,-5,20,c1,c2,c3); // controls move 5 units to the left of c0
        ctlposition(c0,10,10);
        . . .
      A function has been added to convert file/directory paths from their
      relative values into fully qualified paths.  The fullpath() function
      takes a single path value (string), and returns a full path version
      if the provided path is relative.  If the path is already fully qualified,
      then that value is return.
      A requester function called ctlrefresh() is available to make indicated
      controls act as triggers to "refresh" the values of other controls on the
      panel.  The function takes a control identifier, and the name of a refresh
      callback function.  When the specified control is altered in any way by
      the user (i.e., mouse clicks, keyboard entry, etc.), the refresh callback
      is invoked with the current value of the control.

      Unlike callbacks used by other requester controls, the callback for
      ctlrefresh() should not return a value.  Instead, control refreshing
      takes place through the use of the setvalue() function.

        filenames;
        counter = 2;
        c1;

        main
        {
            . . .
            filenames = matchfiles("c:\\temp","*.*");

            c0 = ctlchoice("Test",counter,@"X","Y","Z"@);
            c1 = ctlstring("Filenames",filenames[counter]);

            ctlrefresh(c0,"refresh");
            . . .
        }

        refresh: value
        {
            setvalue(c1,filenames[value]);
        }

      Note the use of the global values.  Because the refresh callback alters
      the values of controls directly, the control identifiers must be visible
      at a global level.  For instance, in the example above, the 'c1' variable
      is made global so that the refresh callback can change its value directly.
      Point and Polygon data types have been given their own set of data members
      and methods.  In each case, additional information is available through
      these additions that cannot be accessed by any other means in LScript.

      The Point data type now responds to the following messages:

          polygon/polygon()

                  this element of the Point data type is overloaded to return
                  different data based on the context.  if the element is
                  accessed directly, then the number of polygons to which the
                  point belongs is returned.  if the method context is invoked,
                  then one or more polygon identifiers will be returned to
                  the caller.  if the point is unassociated with any polygon,
                  the data member context will return zero (0), and the method
                  context will return 'nil'.

                  the data member context is read-only.

          x, y, z

                  these three data members hold the position of the point
                  along the indicated axis.  these values can be read, or
                  the can be assigned to.  assignments will alter the
                  position of the point.

      The Polygon data type now responds to the following messages:

          surface

                  this is the surface name currently assigned to the polygon.
                  this data member can be read, and it can be assigned to.
                  if the surface name assigned does not exist, LScript will
                  first create it.

          pointCount

                  an integer value that contains the number of points that
                  comprise the polygon.  this data member is read-only.

          layer

                  an integer value between 1 and 10 that indicates the
                  Modeler layer in which the polygon lives.  this data
                  member is read-only.

          points[]

                  an array of Point identifiers that contain all the points
                  that comprise the polygon.  it has 'pointCount' elements.
                  the elements in this data member are read-only.

          isCurve()

                  returns a true or false value that indicates whether
                  the polygon is a curve (true) or a face (false).

          hasCCEnd()

                  if the polygon is a curve, this method will return
                  true if it contains a control point at its tail.

          hasCCStart()

                  if the polygon is a curve, this method will return
                  true if it contains a control point at its head.

          setPoints()

                  this method can be used to set the points that
                  comprise the polygon.  it accepts an array reference
                  containing pre-existing point ids, a list of
                  pre-existing pointer ids, or a series of one or
                  more vector values that the method will first
                  convert into points and then apply them to the
                  polygon.

      Point and Polygon messages are only legal during a mesh edit
      session (i.e., between calls to editBegin() and editEnd()).
      The LScript pre-processor now understands a simple form of conditional
      code compilation.  Using the new @if, @else, and @end directives, you
      can instruct the compiler to include--or exclude--code from compilation.

      The @if test expects an identifer, followed by an optional equality
      operator, and an optional value to test.  If the optional value is
      omitted, then the test will succeed if the identifer exists in either
      the pre-processor's macro list, or in the LScript environment
      (enviromental testing is not performed on the Mac).

        . . .
        @if LOCALMODE
            info("running in local mode");
        @end
        . . .

      If the test value exists without an equality operator, then equality
      is tested by default.  Recognized equality test operators are "<", ">",
      "<=", ">=", "==", and "!=".

        . . .
        @define TEST 45
        . . .
        @if TEST > 40
            info("TEST > 40");
        @else
            info("TEST <= 40");
        @end
        . . .

      A special identifier "version" is recognized that will evaluate to
      the current version of the LScript plug-in.  This allows code to be
      compiled based upon the version of LScript.

        . . .
        @if version < 1.4
            scl = degree(obj.getScale(time));
        @else
            scl = obj.getScale(time);
        @end
        . . .

      In addition, a "not" operator can be applied to any test to invert the
      result.

        . . .
        @if not version 1.4
            scl = degree(obj.getScale(time));
        @else
            scl = obj.getScale(time);
        @end
        . . .

### BUG FIXES

      LScript/IF was providing the 'start' and 'end' times for a frame as integer
      values instead of floating point.
      LScript/IF was freeing memory that it didn't own, causing crashing of Layout
      under certain conditions and platforms.
      Layout options() processing was not detecting an active requester interface
      upon exit (i.e., reqbegin() was called, but reqend() was not before the
      function terminated), leaving certain internal flags set.  These flags
      then prevented other functions (notably, process()) from being executed.
      This has been corrected.
      The LScript/PT illuminate() function was incorrectly requiring three
      parameters, when only two were actually required (light id and world
      coordinate position).
      LScript/PT was not returning the result code from the illuminate()
      call.
      The LScript/PT raySource[] data member was not properly accessible.
      The File Object Agent's writeln() method was not performing proper
      error detection when bad point or polygon identifiers were provided
      for printing.
      The File Object Agent's writeln() method did not properly recognize
      point and polygon identifiers contained within arrays.
      Added better size checking in the parser for build stacks.
      The redo() command was missing from the run-time system.
      The if/else construct was broken in the run-time system.
      The number() function contained some old code that would
      return an integer instead of a floating-point value under
      certain conditions.
      The random() function was returning the wrong data type under certain
      conditions.
      Default value assignments, introduced in v1.3, were causing internal
      code problems when floating-point values were returned from functions.
      LScript was not properly recognizing IPC queue variables when used in
      mathematical operations.
      Some division operations involving arrays were multiplying instead
      of dividing.
      Some returned rotational values (i.e., param(ROTATION,...)) were
      being returned as radians instead of degrees.  This was causing
      problems with functions that expected degree-based parameters
      (i.e., set(ROTATION,...)).
      Garbage collection of character strings was not functioning properly,
      resulting in excessive memory consumption in long-running scripts.
      Further synchronization problems existed between the script
      compilers and the run-time plug-ins.
      Some here-to-fore undiscovered stack handling problems existed
      in the Layout LScript global Object Agents (Scene, Bones, etc.).
      This has been corrected.
      Some memory allocations in the requester mechanism were not
      being cleared, leaving undefined values in certain areas.
      Enhancements introduced in v1.4 uncovered this oversight.
      The select() function has been renamed to selector() to
      resolve a conflict with one of LScripts reserved keywords.