  The Mops Custom Objects Writers Guide
  Randolf Schultz (rschultz@informatik.uni-rostock.de)
  23.5.1999

  This document describes the odds and ends of writing a custom object
  for The Mops.
  ______________________________________________________________________

  Table of Contents:

  1.      Introduction

  2.      Preparation

  3.      Functions

  3.1.    Initialization

  3.2.    Create

  3.3.    Delete

  3.4.    Copy

  3.5.    Draww - Draw Wireframe

  3.6.    Draws - Draw Surface

  3.7.    Drawh - Draw Handles

  3.8.    Wrib - Write RIB

  3.9.    Write/Read

  3.10.   Notification

  3.11.   GetPoint, Single Point Editing

  3.12.   Getprop - Get Properties

  4.      Custom Objects from the Tcl Side of Life

  4.1.    The Custom Menu

  5.      Typechecking Custom Objects

  6.      Compiling, Installing, Loading a Custom Object
  ______________________________________________________________________

  1.  Introduction

  You should take a look at (or print out) the files csphere.c and
  csphere.tcl from the src directory of the Mops distribution.  They
  contain a more or less documented example custom object that does
  nothing exciting, but implements a simple sphere.  They may serve as
  starting point for your own custom object. Additionally, take a look
  at the modules extrude.c, sweep.c, revolve.c or icurve.c. They
  implement custom objects too and support notification, in contrast to
  the CSphere.  The module icurve.c, additionally, shows the use of
  single point editing and the provide mechanism.

  2.  Preparation

  You decided to implement a new custom object?  Fine, here is what to
  do first:

  o  include mops.h

  o  Pick a short, pregnant, descriptive, non colliding name for your
     object. To avoid clashes I suggest to precede the name with the
     initials of the author (yours) as it is tradition with RenderMan
     shaders.

  o  Create a structure that holds the parameters of your object.  This
     will be referred to later as the custom object.

  Now you are ready to implement some functions as they are described in
  the next sections of the manual.

  3.  Functions

  A custom object implements a set of functions that are called from the
  Mops core now and then.

  Some functions are mandatory, some that would implement point editing
  support or notification are not.

  3.1.  Initialization

  Initialization of a custom object is done by a function named
  $name_Init(Tcl_Interp *interp).  Note, that $name is the name of the
  shared object (the file name!) that gets loaded, and that the first
  character of the function name has to be upcase, regardless of the
  case of the file name.

  This function is called once when the custom objects code gets loaded
  into The Mops.

  What does the initialization do? First, it registers the custom object
  at the Mops core by calling:

  mops_register_custom_objecttype().

  If the registration fails, the function returns a nonzero value.  You
  should really check this return value, and if it is nonzero call
  mops_error() with it and return TCL_ERROR.  The parameters of the
  function are as follows:

  o  char *name; pointer to a global string containing, the custom
     object name

  o  mops_create_custom_cb *customcreate_cb; pointer to a function of
     type mops_create_custom_cb

  o  and so on, the functions will be explained in the next sections.

  Some new Tcl commands may be registered now by calling
  Tcl_CreateCommand(). Usually new commands need to be created to get or
  set object type specific properties.

  Finally, it is time to load some Tcl code from Tcl files into the
  interpreter. This code implements the property GUIs of the object type
  specific properties (do not worry, this is easy).

  3.2.  Create

  The creation callback (of type mops_create_custom_cb) gets called on
  creation of your custom object.

  You should allocate memory for your new object, and initialize it
  properly. Argc and argv are the command line of the crtOb custom
  $yourtype command.  (The user may deliver additional arguments to this
  command.) The last argument is a pointer to a pointer where you have
  to put the address of your new custom object into.

  3.3.  Delete

  This callback is called, when an objects needs to be deleted.  You
  should free all memory allocated for the custom object, the argument c
  points to one of your custom objects.

  3.4.  Copy

  You should allocate a new object and copy the custom object pointed to
  by source to the new allocated memory, and finally return a pointer to
  the new memory in dest.

  3.5.  Draww - Draw Wireframe

  Draw your custom object as wireframe.  You do not get a pointer to
  your custom object as parameter, but a pointer to a Mops object, which
  is two steps higher in the object hierarchy!

  This is, because you may freely decide whether to use the standard
  attributes stored with every Mops object.  These are for instance
  color, opacity and the transformations.

  If you want to use them as most other Mops objects simply call
  mops_render_attributes().

  See the example source for information on how to finally get to your
  custom object.

  3.6.  Draws - Draw Surface

  This callback is basically the same as the draww callback, but the
  user expects to get a much better represention of the object.

  3.7.  Drawh - Draw Handles

  This callback is not mandatory, and needs just to be implemented if
  your object supports single point editing.  If you want to do this,
  you should draw with points just the points of your object that may be
  modified by a single point editing action if this function gets
  called.

  3.8.  Wrib - Write RIB

  This callback is for exporting your object to a RIB.  Just like the
  drawing callbacks you get a pointer to a Mops object and not to your
  custom object, so that you can choose whether to use the standard
  attributes and shaders.

  Using mops_wrib_attributes() and mops_wrib_shaders() you can mimic the
  behaviour of most other Mops objects.

  3.9.  Write/Read

  These callbacks are for writing and reading Mops scene files.  As you
  can see in the example source, simple fprintf(), fscanf() calls are
  currently in use.  Note, that you do not have to worry about your
  child objects, if there are any, they will be saved automagically.

  3.10.  Notification

  The notification callback is for custom objects that rely on other
  objects to be childs of them (see sweep, extrude or revolve).

  The notification callback is to inform you, that something below your
  custom object has changed, and you should probably adapt the custom
  object to the change. The Revolve object e.g. redoes the revolution.

  3.11.  GetPoint, Single Point Editing

  This callback enables all the single point editing facilities
  (including the selection mechanism for single points) for your object.

  With this callback you get an object and a point in the local space of
  that object. You are asked to search through your internal structures
  for points of yours that match the given coordinates.  If there are
  such points you should build an array of pointers to your points as
  the following example does:

  ______________________________________________________________________
  {
   double *control = NULL, min_distance = mops_prefs.pick_epsilon,
           distance = 0.0;

   /* first, we clear the old array */
   if(mops_point_edit_coords) free(mops_point_edit_coords);
   mops_point_edit_coords = NULL;

   /* now we scan our points for the given coordinates*/
   control = array_of_your_points;
   for(i = 0; i < max_points; i++)
      {
       distance = MOPS_VLEN((objX - control[j]),
                             (objY - control[j+1]),
                             (objZ - control[j+2]));
       if(distance < min_distance)
          {
            pecoords = &(control[j]);
            min_distance = distance;
          }

        j += 3;
      }

    if(!pecoords)
      return MOPS_OK;

    /* are the points homogenous? */
    mops_point_edit_coords_homogenous = MOPS_FALSE;

    /* now we create the new array */
    if(!(mops_point_edit_coords = calloc(1,sizeof(double*))))
      return MOPS_OUTOFMEM;
    mops_point_edit_coords_number = 1;
    mops_point_edit_coords[0] = pecoords;

  }
  ______________________________________________________________________

  The code above does just handle the selection of a single point, it is
  possible, however, to put an arbitrary number of points in the array
  at once!  The following global variables are in use:
  mops_point_edit_coords (the adress of the array),
  mops_point_edit_coords_number (an integer that tells The Mops, how
  many pointers are in mops_point_edit_coords),
  mops_point_edit_coords_homogenous (a flag that tells The Mops, wether
  the points are homogenous or not; note, that there can only be points
  of one type in the array).

  Also note, that the Mops core will poke into the memory you pointed it
  to later. The core expects the points itself to be arrays of doubles:

  ______________________________________________________________________
  double a_non_homogenous_point[3];
  double a_homogenous_point[4];
  ______________________________________________________________________

  3.12.  Getprop - Get Properties

  In this callback you tell the Tcl core of The Mops, which properties
  your custom object has. You do this by filling some special global
  lists in the Tcl context using Tcl_SetVar() or similar Tcl functions.

  This function will be called whenever a single custom object has been
  selected in the selection listbox.

  The following lists in the Tcl context need to be updated for every
  property: propertyList, propertyProcList, propertyGetProcList,
  propertySetProcList and propertyDataList.

  Each object has automagically the Transformations and the Attributes
  property assigned. You can change that by unsetting the lists prior to
  your changes (using Tcl_UnsetVar() e.g.).

  There are functions prepared for you that add the right things for
  shader properties to the lists: mops_add_surfshader_property(); e.g.

  The list propertyList holds the names of the Properties as they shall
  appear in the property listbox.

  The list propertyProcList holds the names of the procedures to call
  for each property that builds the corresponding property GUI in the
  Arguments canvas.  Unless the property GUI marks itself as static this
  procedure will be called on every apply.

  The lists propertyGetProcList and propertySetProcList contain
  procedure calls that are used to transfer parameters from C to Tcl
  (Get) and from Tcl to C (Set, called on Apply).  These functions need
  to be implemented as Tcl commands, see CSphere_set_attrprop_tcmd() and
  CSphere_get_attrprop_tcmd() for an example. Also note how these
  commands are created in the Init function of the csphere custom
  object.

  Finally, the list propertyDataList contains the names of the arrays
  where each property holds its parameters.

  4.  Custom Objects from the Tcl Side of Life

  Yes, custom objects need some Tcl code too.

  Take a look at csphere.tcl. This Tcl code creates a single procedure
  that, in turn, creates the Property GUI of the CSphereAttribute
  property.  The code is really simple and creates a static GUI
  consisting of a label and four entries for parameters. The procedure
  will be executed whenever the user tries to manipulate the
  CSphereAttribute property of a csphere custom object.

  What means static in this context? Static means that the property GUI
  will not be rebuild (read, the procedure csphere_buildCSphereAttrGUI
  will not be called) on every apply operation of the user.  The default
  behaviour of property GUI's is, that they are rebuilt for every apply
  operation, this allows for really dynamic GUI's to be generated.

  Now on to the other procedure calls.

  A property GUI is usually organized in list form.  The single list
  elements are mostly built by calling a single Tcl command of the Mops
  core.  A list element of a property GUI is referred to as entry from
  now on.

  The template for such a command (that creates a single line of a
  property GUI) is as follows:

  add$typeEntry $w $array $name

  Type is the type of the entry. Predefined and heavily used by the rest
  of The Mops are: Text, Param, Menu, Check, Color, File, String and
  Command.  Depending on the type, the actual parameters are a bit
  different but this is documented in detail later on...

  $w is the window the new entry should be created in.  Since the create
  property GUI procedure will be called with this information as
  argument, passing it on to the create entry procedure is the logical
  consequence and simple to do.

  $array is the name of the (global) array where the parameters that
  should be edited are actually stored.

  $name is the name of the variable in the global array $array that this
  entry should manipulate.  In addition, this name is often used in a
  label to mark the entry. You should use descriptive and not too long
  variable names.

  You are, of course, not tied to the aforementioned entries.  The
  seashell custom object, for instance, implements an own type, an entry
  consisting of a slider, that is even able to issue apply operations
  while dragging the slider.  However, how to program your own type of
  entry is not documented yet, UTSL!

  Here is now the documentation for all core entry types:

  addTextEntry $w $f $text

  adds a line containing the text $text to the property GUI.  $f is
  required to generate window names, use e1, e2 etc.

  addParamEntry $w $array $name

  creates the standard parameter manipulation entry consisting of a
  label, two buttons for quick parameter manipulation by doubling,
  dividing the value, and finally an entry for direct manipulation.

  addMenuEntry $w $array $name $list

  adds a menu button, that toggles between the elements of the list
  $list. The variable $name always contains the index of the selected
  entry.

  addCheckEntry $w $array $name

  adds a single check button to the GUI. The variable $name will be set
  to either 0 or 1 according to the state of the check button.

  addColorEntry $w $array $name

  adds a color selection facility. The color values will be written to
  variables named ${name}_R, ${name}_G, ${name}_B and ${name}_A.

  addFileEntry $w $array $name

  adds a string entry and a small button, that starts the standard file
  requester, handy for strings that contain file names.

  addStringEntry $w $array $name

  adds a simple string entry.

  addCommandEntry $w $f $text $command

  adds a big button labelled with $text that starts the command
  $command.  $f is similar to text entries a name for a window, I
  suggest to name the windows c1, c2 etc.

  4.1.  The Custom Menu

  You may link additional functionality of your custom object to entries
  in the custom menu.

  The following example code snippet shows how to do that.

  ______________________________________________________________________
  global prefs
  $prefs(cm) add cascade -menu $prefs(cm).foo -label "Foo"
  set m [menu $prefs(cm).foo]
  $m add command -label "Foo(l)" -command fooproc
  ______________________________________________________________________

  Note, that you should always create a new submenu using a cascade
  entry instead of creating entries directly in the custom menu.

  5.  Typechecking Custom Objects

  If you write own functions (apart from the ones, that you supply with
  the initialization) that get objects directly from the selection, you
  need to check if the selected object is of correct type.

  The following code fragment shows how you can be sure an object is
  really a custom object of yours:

  ______________________________________________________________________
  /* global variables */
  static char *foo_name = "Foo";
  static char *foo_customid = NULL;

  Foo_Init()
  {

    mops_register_custom_objecttype(...);

    /* get custom-id from name */
    entry = Tcl_FindHashEntry(&mops_custom_id_ht, foo_name);
    if (entry != NULL)
      {
        foo_customid = Tcl_GetHashValue(entry);
      }
    else
      {
        mops_error(interp, MOPS_HASH, fname, NULL);
        return TCL_OK;
      }

  }

  void
  some_function()
  {
   mops_list_object *sel = mops_current_selection;
   mops_object *o = NULL;
   mops_custom_object *custom = NULL;
   foo *my_object = NULL;

   /* is there something selected? */
   if(!sel)
    return TCL_OK;

   /* get mops_object from selection */
   o = sel->object;
   if(!o)
    return TCL_OK;

   /* is it a custom object? */
   if(o->type != MOPS_OTCUSTOM)
    return TCL_OK;

   /* get mops_custom_object from mops_object */
   custom = (mops_custom_object *)o->object;

   if(!custom)
    return TCL_OK;

   if(custom->type != foo_customid)
    return TCL_OK;

   /* let him pass... */

   my_object = (foo *)custom->object;
  ______________________________________________________________________

  6.  Compiling, Installing, Loading a Custom Object

  Mops custom objects need to include mops.h, mops.h in turn includes
  Togl (OpenGL, Tcl/Tk) and RI headers.  When compiling your custom
  object, all you need to do is to make sure the compiler finds all
  includes.  See the Makefile of The Mops on how to collect all
  necessary information to build -I directives.  Compile your source
  with the -c switch.  Then use the right switch for your compiler
  (-shared ?) to make a shared object (.so) from the .o and you should
  be ready to test (>cc -shared -o foo.so foo.o).

  Install the shared object along with the Tcl file containing the
  property GUI procedure and other stuff.

  To load a custom object use the appropriate menu entry in the File
  menu or the lc (load custom) command. Both methods will automatically
  change the working directory to the location of the shared object to
  allow it to find the accompanying tcl file more easily. Do not use the
  load command!

