Next Previous Contents

3. Functions

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

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

3.1 Initialization (mandatory)


int
Yourname_Init(Tcl_Interp *interp)

Initialization of a custom object is done by a function named $yourname_Init(Tcl_Interp *interp). Note, that $yourname is the filename of the shared object that gets loaded, and that the first character of the name has to be upcase, regardless of the case of the file name. It is a good idea to mimic the naming strategy of the csphere example.

The initialization is called once when the custom objects code gets loaded into Ayam.

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

ay_otype_register().


int ay_otype_register(char *name,
                      ay_createcb  *crtcb,
                      ay_deletecb  *delcb,
                      ay_copycb    *copycb,
                      ay_drawcb    *drawcb,
                      ay_drawcb    *drawhcb,
                      ay_drawcb    *shadecb,
                      ay_propcb    *setpropcb,
                      ay_propcb    *getpropcb,
                      ay_getpntcb  *getpntcb,
                      ay_readcb    *readcb,
                      ay_writecb   *writecb,
                      ay_wribcb    *wribcb,
                      ay_bbccb     *bbccb,
                      unsigned int *type_index);

If the registration fails, the function returns a nonzero value. You should really check this return value, and if it is nonzero call ay_error() with it and return TCL_ERROR.

The parameters of the function ay_otype_register() are as follows:

After the call to ay_otype_register(), callbacks for special functionality (notification, conversion, or provide mechanism) may be registered.

Furthermore, you may specify with a call to ay_matt_nomaterial(); that your new object type shall not be associable with material objects, e.g. if it is just a geometric helper object that does not get exported to RIB.

Some new Tcl commands for additional functionality not covered by the provided standard and special callbacks may be registered now by calling Tcl_CreateCommand().

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).

Csphere example:


/* note: this function _must_ be capitalized exactly this way
 * regardless of filename (see: man n load)!
 */
int
Csphere_Init(Tcl_Interp *interp)
{
 int ay_status = AY_OK;
 char fname[] = "csphere_init";

  ay_status = ay_otype_register(csphere_name,
                                csphere_createcb,
                                csphere_deletecb,
                                csphere_copycb,
                                csphere_drawcb,
                                NULL, /* no points to edit */
                                csphere_shadecb,
                                csphere_setpropcb,
                                csphere_getpropcb,
                                NULL, /* no picking */
                                csphere_readcb,
                                csphere_writecb,
                                csphere_wribcb,
                                csphere_bbccb,
                                &csphere_id);

  if(ay_status)
    {
      ay_error(AY_ERROR, fname, "Error registering custom object!");
      return TCL_ERROR;
    }

  /* source csphere.tcl, it contains Tcl-code to build
     the CSphere-Attributes Property GUI */
  if((Tcl_EvalFile(interp, "csphere.tcl")) != TCL_OK)
     {
       ay_error(AY_ERROR, fname,
                  "Error while sourcing \\\"csphere.tcl\\\"!");
       return TCL_OK;
     }

  ay_error(AY_EOUTPUT, fname,
           "CustomObject \\\"CSphere\\\" successfully loaded.");

 return TCL_OK;
} /* Csphere_Init */

The following sections describe the functions that are parameters of ay_otype_register().

3.2 Create (mandatory)


int yourname_createcb(int argc, char *argv[], ay_object *o);

In this callback you should allocate memory for your new object, and initialize it properly. Argc and argv are the command line of the crtOb yourname Tcl-command (invoked e.g. by the main menu entry Create/Custom Object/yourname, by Tcl scripts, or directly from the console). The user may deliver additional arguments to this command, which may be evaluated by this callback.

Furthermore, in this callback you can adjust the attribute property of the newly created object, e.g. hiding it or its children initially. Just set the appropriate flags in the ayam_object pointed to by o. Look up the definition of ay_object, in ayam.h to see, what may be adapted.

If you want your object be able to have child objects you should set the o->parent attribute to AY_TRUE. You may create first child objects in your create callback. But note, that each level in the scene hierarchy needs to be terminated properly with a so called EndLevel object. Such an object might be created easily using ay_object_crtendlevel();:


 ay_object *my_child = NULL;

  /* create my_child here */
  ...

  /* link my_child */
  o->down = my_child;
  /* terminate level */
  ay_object_crtendlevel(&(my_child->next));

If you do not create child objects immediately, but set o->parent to true, Ayam will create the EndLevel object automatically for you.

Csphere example:


int
csphere_createcb(int argc, char *argv[], ay_object *o)
{
 csphere_object *csphere = NULL;
 char fname[] = "crtcsphere";

  if(!o)
    return AY_ENULL;

  if(!(csphere = calloc(1, sizeof(csphere_object))))
    {
      ay_error(AY_EOMEM, fname, NULL);
      return AY_ERROR;
    }

  csphere->closed = AY_TRUE;
  csphere->is_simple = AY_TRUE;
  csphere->radius = 1.0;
  csphere->zmin = -1.0;
  csphere->zmax = 1.0;
  csphere->thetamax = 360.0;

  o->refine = csphere;

 return AY_OK;
} /* csphere_createcb */

3.3 Delete (mandatory)


int yourname_deletecb(void *c);

In this callback you should free all memory allocated for the custom object, the argument c points to one of your custom objects. No type check necessary.

Csphere example:


int
csphere_deletecb(void *c)
{
 csphere_object *csphere = NULL;

  if(!c)
    return AY_ENULL;    

  csphere = (csphere_object *)(c);

  free(csphere);

 return AY_OK;
} /* csphere_deletecb */

3.4 Copy (mandatory)


int yourname_copycb(void *src, void **dst);

The copy callback is mandatory too, it is vital for clipboard and undo functionality. 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 dst. The argument src points to one of your custom objects. No type check necessary.

Csphere example:


int
csphere_copycb(void *src, void **dst)
{
 csphere_object *csphere = NULL;

  if(!src || !dst)
    return AY_ENULL;

  if(!(csphere = calloc(1, sizeof(csphere_object))))
    return AY_EOMEM; 

  memcpy(csphere, src, sizeof(csphere_object)); 

  *dst = (void *)csphere;

 return AY_OK;
} /* csphere_copycb */

3.5 Draw


int yourname_drawcb(struct Togl *togl,  ay_object *o);

In this callback you should draw your custom object. You do not get a pointer to your custom object as parameter, but a pointer to a Ayam object, which is a step higher in the object hierarchy!

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

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

Csphere example (extract):


int
csphere_drawcb(struct Togl *togl, ay_object *o)
{
 csphere_object *csphere = NULL;

...

  if(!o)
    return AY_ENULL;

  csphere = (csphere_object *)o->refine;

  if(!csphere)
    return AY_ENULL;

  radius = csphere->radius;

...

 return AY_OK;
} /* csphere_drawcb */

3.6 Shade


int yourname_shadecb(struct Togl *togl,  ay_object *o);

This callback is basically the same as the draw callback, but the user expects to get a shaded represention of the object.

3.7 Drawh - Draw Handles


int yourname_drawhcb(struct Togl *togl,  ay_object *o);

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 glPoint() just the points of your object that may be modified by a single point editing action in this callback.

3.8 SetProp - Set Properties


int yourname_setpropcb(Tcl_Interp *interp, int argc, char *argv[],
ay_object *o);

Using this callback you copy data of your object type specific properties from the Tcl to the C context. Note the use of Tcl_IncrRefCount() and Tcl_DecrRefCount() to avoid memory leaks.

Also note, that if your object is used as parameter object for a tool object you should inform the tool object (your parent) about changes now using:


ay_status = ay_notify_parent(void);

Csphere example:


/* Tcl -> C! */
int
csphere_setpropcb(Tcl_Interp *interp, int argc, char *argv[], ay_object *o)
{
 char *n1 = "CSphereAttrData";
 Tcl_Obj *to = NULL, *toa = NULL, *ton = NULL;
 csphere_object *csphere = NULL;
 int itemp = 0;

  if(!o)
    return AY_ENULL;

  csphere = (csphere_object *)o->refine;
  
  toa = Tcl_NewStringObj(n1,-1);
  ton = Tcl_NewStringObj(n1,-1);

  Tcl_SetStringObj(ton,"Closed",-1);
  to = Tcl_ObjGetVar2(interp,toa,ton,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
  Tcl_GetIntFromObj(interp,to, &itemp);
  csphere->closed = (char)itemp;

  Tcl_SetStringObj(ton,"Radius",-1);
  to = Tcl_ObjGetVar2(interp,toa,ton,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
  Tcl_GetDoubleFromObj(interp,to, &csphere->radius);

  Tcl_SetStringObj(ton,"ZMin",-1);
  to = Tcl_ObjGetVar2(interp,toa,ton,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
  Tcl_GetDoubleFromObj(interp,to, &csphere->zmin);

  Tcl_SetStringObj(ton,"ZMax",-1);
  to = Tcl_ObjGetVar2(interp,toa,ton,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
  Tcl_GetDoubleFromObj(interp,to, &csphere->zmax);

  Tcl_SetStringObj(ton,"ThetaMax",-1);
  to = Tcl_ObjGetVar2(interp,toa,ton,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);
  Tcl_GetDoubleFromObj(interp,to, &csphere->thetamax);

  if((fabs(csphere->zmin) == csphere->radius) &&
     (fabs(csphere->zmax) == csphere->radius) &&
     (fabs(csphere->thetamax) == 360.0))
    {
      csphere->is_simple = AY_TRUE;
    }
  else
    {
      csphere->is_simple = AY_FALSE;
    }

  Tcl_IncrRefCount(toa);Tcl_DecrRefCount(toa);
  Tcl_IncrRefCount(ton);Tcl_DecrRefCount(ton);

 return AY_OK;
} /* csphere_setpropcb */

3.9 GetProp - Get Properties


int yourname_getpropcb(Tcl_Interp *interp, int argc, char *argv[],
ay_object *o);

Using this callback you copy data of your object type specific properties from the C to the Tcl context.

Csphere example:


/* C -> Tcl! */
int
csphere_getpropcb(Tcl_Interp *interp, int argc, char *argv[], ay_object *o)
{
 char *n1="CSphereAttrData";
 Tcl_Obj *to = NULL, *toa = NULL, *ton = NULL;
 csphere_object *csphere = NULL;

  if(!o)
    return AY_ENULL;

  csphere = (csphere_object *)(o->refine);

  toa = Tcl_NewStringObj(n1,-1);

  ton = Tcl_NewStringObj(n1,-1);

  Tcl_SetStringObj(ton,"Closed",-1);
  to = Tcl_NewIntObj(csphere->closed);
  Tcl_ObjSetVar2(interp,toa,ton,to,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);

  Tcl_SetStringObj(ton,"Radius",-1);
  to = Tcl_NewDoubleObj(csphere->radius);
  Tcl_ObjSetVar2(interp,toa,ton,to,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);

  Tcl_SetStringObj(ton,"ZMin",-1);
  to = Tcl_NewDoubleObj(csphere->zmin);
  Tcl_ObjSetVar2(interp,toa,ton,to,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);

  Tcl_SetStringObj(ton,"ZMax",-1);
  to = Tcl_NewDoubleObj(csphere->zmax);
  Tcl_ObjSetVar2(interp,toa,ton,to,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);

  Tcl_SetStringObj(ton,"ThetaMax",-1);
  to = Tcl_NewDoubleObj(csphere->thetamax);
  Tcl_ObjSetVar2(interp,toa,ton,to,TCL_LEAVE_ERR_MSG | TCL_GLOBAL_ONLY);

  Tcl_IncrRefCount(toa);Tcl_DecrRefCount(toa);
  Tcl_IncrRefCount(ton);Tcl_DecrRefCount(ton);

 return AY_OK;
} /* csphere_getpropcb */

3.10 GetPoint, Single Point Editing


int yourname_getpntcb(ay_object *o, double *p);

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 coordinates given in "p". If there are such points you should build an array of pointers to your points as the following example does:


yourname_getpntcb(ay_object *o, double *p)
{
 double *control = NULL, min_distance = ay_prefs.pick_epsilon,
         distance = 0.0;

 /* first, we clear the old array */
 if(ay_point_edit_coords) free(ay_point_edit_coords);
 ay_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 = AY_VLEN((objX - control[j]),
                        (objY - control[j+1]),
                        (objZ - control[j+2]));
     if(distance < min_distance)
        {
          pecoords = &amp;(control[j]);
          min_distance = distance;
        }

      j += 3;
    }

  if(!pecoords)
    return AY_OK;

  /* are the points homogenous? */
  ay_point_edit_coords_homogenous = AY_FALSE;

  /* now we create the new array */
  if(!(ay_point_edit_coords = calloc(1,sizeof(double*))))
    return AY_OUTOFMEM;
  /* and fill it */
  ay_point_edit_coords_number = 1;
  ay_point_edit_coords[0] = pecoords;

 return AY_OK;
} /* */

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! This is necessary for the special case of (p[0] == DBL_MIN) && (p[1] == DBL_MIN) && (p[2] == DBL_MIN). If all elements of "p" are "DBL_MIN" you should put all editable points of the object into the array (the user wants to select all points).

The following global variables are in use: ay_point_edit_coords (the adress of the array), ay_point_edit_coords_number (an integer that tells Ayam, how many pointers are in ay_point_edit_coords), ay_point_edit_coords_homogenous (a flag that tells Ayam, wether the points are homogenous or not; note, that there can only be points of one type in the array).

Also note, that the Ayam 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.11 Write/Read (mandatory)


int yourname_readcb(FILE *fileptr, ay_object *o);

These callbacks are for writing and reading Ayam 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.

Csphere example:


int
csphere_readcb(FILE *fileptr, ay_object *o)
{
 csphere_object *csphere = NULL;
 int itemp = 0;
 if(!o)
   return AY_ENULL;

  if(!(csphere = calloc(1, sizeof(csphere_object))))
    { return AY_EOMEM; }

  fscanf(fileptr,"%d\n",&itemp);
  csphere->closed = (char)itemp;
  fscanf(fileptr,"%lg\n",&csphere->radius);
  fscanf(fileptr,"%lg\n",&csphere->zmin);
  fscanf(fileptr,"%lg\n",&csphere->zmax);
  fscanf(fileptr,"%lg\n",&csphere->thetamax);

  if((fabs(csphere->zmin) == csphere->radius) &&
     (fabs(csphere->zmax) == csphere->radius) &&
     (fabs(csphere->thetamax) == 360.0))
    {
      csphere->is_simple = AY_TRUE;
    }
  else
    {
      csphere->is_simple = AY_FALSE;
    }

  o->refine = csphere;

 return AY_OK;
} /* csphere_readcb */

int
csphere_writecb(FILE *fileptr, ay_object *o)
{
 csphere_object *csphere = NULL;

  if(!o)
    return AY_ENULL;

  csphere = (csphere_object *)(o->refine);

  fprintf(fileptr, "%d\n", csphere->closed);
  fprintf(fileptr, "%g\n", csphere->radius);
  fprintf(fileptr, "%g\n", csphere->zmin);
  fprintf(fileptr, "%g\n", csphere->zmax);
  fprintf(fileptr, "%g\n", csphere->thetamax);

 return AY_OK;
} /* csphere_writecb */

3.12 Wrib - Write RIB


int yourname_wribcb(char *file, ay_object *o);

This callback is for exporting your object to a RIB. Just use the appropriate Ri-calls. Just like the drawing callbacks you get a pointer to a Ayam object and not to your custom object. Csphere example:


int
csphere_wribcb(char *file, ay_object *o)
{
 csphere_object *csphere = NULL;

...

  if(!o)
   return AY_ENULL;

  csphere = (csphere_object*)o->refine;


  if(!csphere->closed)
  {
    RiSphere((RtFloat)csphere->radius,
             (RtFloat)csphere->zmin,
             (RtFloat)csphere->zmax,
             (RtFloat)csphere->thetamax,
             NULL);
  }

...

 return AY_OK;
} /* csphere_wribcb */

3.13 BBC - Bounding Box Calculation


int yourname_bbccb(ay_object *o, double *bbox, int *flags);

This callback is for the calculation of bounding boxes. "bbox" points to an array of 24 doubles (describing 8 points), the bounding box. You may put additional information into "flags" to tell the core that you:

  1. have a regular bounding box (leave flags at zero)
  2. have a regular bounding box but the boxes of the children should be discarded (set flags to 1)
  3. have no own bbox, but children have (set flags to 2)

Csphere example:


int
csphere_bbccb(ay_object *o, double *bbox, int *flags)
{
 double r = 0.0, zmi = 0.0, zma = 0.0;
 csphere_object *csphere = NULL;

  if(!o || !bbox)
    return AY_ENULL;

  csphere = (csphere_object *)o->refine; 

  r = csphere->radius;
  zmi = csphere->zmin;
  zma = csphere->zmax;

  /* XXXX does not take into account ThetaMax! */

  /* P1 */
  bbox[0] = -r; bbox[1] = r; bbox[2] = zma;
  /* P2 */
  bbox[3] = -r; bbox[4] = -r; bbox[5] = zma;
  /* P3 */
  bbox[6] = r; bbox[7] = -r; bbox[8] = zma;
  /* P4 */
  bbox[9] = r; bbox[10] = r; bbox[11] = zma;

  /* P5 */
  bbox[12] = -r; bbox[13] = r; bbox[14] = zmi;
  /* P6 */
  bbox[15] = -r; bbox[16] = -r; bbox[17] = zmi;
  /* P7 */
  bbox[18] = r; bbox[19] = -r; bbox[20] = zmi;
  /* P8 */
  bbox[21] = r; bbox[22] = r; bbox[23] = zmi;

 return AY_OK;
} /* csphere_bbccb */

3.14 Notification


int yourname_notifycb(ay_object *o);

The notification callback is for custom objects that rely on other objects to be children of them (e.g. 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.

Notification callbacks have to be registered in the initialization callback using:


int ay_notify_register(ay_notifycb *notcb, unsigned int type_id);

3.15 Conversion


int yourname_convertcb(ay_object *o);

Conversion callbacks are in use for objects that may be converted to objects of a different type, e.g. the interpolating curve object (ICurve) may be converted to a NURBCurve object, or the Revolve object may be converted to a NURBPatch object.

In the conversion callback a new object should be created from the object pointed to by "o". Furthermore, this object needs to be linked into the scene using: "ay_object_link()". See "icurve.c/ay_icurve_convertcb()" for an example.

Conversion callbacks have to be registered in the initialization callback using:


int ay_convert_register(ay_convertcb *convcb, unsigned int type_id);

3.16 Provide


int yourname_providecb(ay_object *o, unsigned int type,
                       ay_object **result);

Provide callbacks are in use for objects that are able to provide objects from a different type. This is very much like the conversion (above) but the objects are not to be linked into the scene but used by e.g. a parent procedural object as parameter for its procedure. For instance, using the provide mechanism a Revolve object is able to revolve an interpolating curve. The argument "type" denotes the desired type of the new object and "result" should be filled with an object of the wanted type. See "icurve.c/ay_icurve_providecb()" for an example.

Provide callbacks have to be registered in the initialization callback using:


int ay_provide_register(ay_providecb *provcb, unsigned int type_id);

3.17 Tree-Drop


int yourname_dropcb(ay_object *o);

The tree-drop callback is for custom objects that want to get notified, when an object is dropped onto them in the tree view to invoke some special actions. This is e.g. used by the material objects that connect to all geometric objects dropped onto them, or by the view object which uses the camera settings from a camera object which is dropped onto the view object.

Tree-drop callbacks have to be registered in the initialization callback using:


int ay_tree_registerdrop(ay_treedropcb *cb, unsigned int type_id)


Next Previous Contents