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.
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().
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));
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 */
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 */
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 */
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 */
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.
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.
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 */
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 */
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 = &(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];
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 */
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 */
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:
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 */
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);
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);
int yourname_providecb(ay_object *o, unsigned int type, ay_object **result);
"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);
int yourname_dropcb(ay_object *o);
Tree-drop callbacks have to be registered in the initialization callback using:
int ay_tree_registerdrop(ay_treedropcb *cb, unsigned int type_id)