points+ Append the list of points from which a function is computed. Can be either a pointer (of type ARG_IS_FPTR) or a list of float values.
points? Return a pointer (of type ARG_IS_FPTR) to the list of points.
point_values? Returns the point values (floats) on the stack.
num_points? Returns the number of points.
call! Invoke the function defined in the subclass.
There are similar messages to each of the above for scalars, vectors, normals, and texture_coords.
It is also important to know that function objects "forget" the values and storage supplied once a "call!" message has been sent. Although they are available for retrival, they are not available for use by a subsequent "call!" message. The storage and/or values must be supplied again before each "call!". The reason for this is to protect against accidental modification of unused components. Consider as an example a function which computes scalars and normals from points. Consider what would happen if this function were used by both a dd_function_filter (which operates on display_data, and provides normals) and a ds_function_filter (which operates on datasets, and does not provide normals). If the function object were first used by the dd_function_filter, the normals would still be present when invoked by the ds_function_filter, and the storage for them would be erroneously modified.
Suppose you wanted to write a function that computed scalar values from the magnitude of vector data. This object currently exists in the function class library and is called vector_mag. Examine the .scr file for vector_mag:
class_generator new: vector_mag
file_name= vector_mag append_file= vector_mag.cls object_name= vector_mag super_object= generic_function append_instance_initialize=`append_init' ;
s_vector new: `append_init' = ( `MODIFY(SCALARS);' ) ;
vector_mag generate!;
It begins in the normal manner, defining the name of the object and the superclass. The significant part is the append_init section, where we identify the modifiable elements (the elements which are computed) via the MODIFY macro. There should be one invocation of this macro for each computed element. The argument should be the name of the component in capital letters. Optionally, this section can invoke the INITIALIZE macro (with identical usage) on any subset of the modifiable elements which should be pre- initialized with original values (like from a display_data object). The INITIALIZE macro should not be used on non- modifiable elements, because those elements are automatically initialized with their initial values. For example, if a function object wanted to compute scalars and normals from points and existing normals, the append_init section would be defined as follows:
s_vector new: `append_init' = ( `MODIFY(SCALARS);', `MODIFY(NORMALS);', `INITIALIZE(NORMALS);', ) ;
Once the .scr file is done, the .cls file can be written. The .cls file need only contain one function, and it must be named "function". It should be declared as follows:
static void function(instance) CLASS_NAME *instance;
For example, vetor_mag.cls contains the following:
#define VEC_DOT(x,y) ((x)[0]*(y)[0] + (x)[1]*(y)[1] + (x)[2]*(y)[2])
#define VEC_MAG(x) sqrt(VEC_DOT(x,x))
static void function(instance) VECTOR_MAG *instance; { int i; if (instance->vectors && instance->scalars) for (i=0;i<instance->num_vectors;i++) instance->scalars[i] = VEC_MAG(instance->vectors + 3*i); }
Note the computation loop. We check for the existance of the fields in question. Some fields may or may not be present. For example, normals would be set by dd_function_filter but not by ds_function_filter. Also, function loops over the number of vectors in this case. It is not necessary to check the size of the scalars storage, because the function filters ensure that the size of any modifiable component will be at least as large as any of the other components.
Although vector_mag defines no new instance variables, subclasses of generic_function may define any additional instance variables it may need to compute its function. For example, dipole_potential does this.