Q1. I am writing a new Jabka object class called Frog, which is a subclass of Amphibian, but I'm not sure what I need to do to incorporate it into Jabka.
A1. Besides writing the object to match the style of the other Jabka objects, you will need to make the following modifications to your installation in order to add the new Jabka object called Frog.
Q2. In writing the C++ code to implement the scheme interface to a method for an object, the procedure check_args() is called. Apparently the first argument to check_args() is a list containing the arguments to the method. What is the second argument to check_args(), for example "l" or "ll"?
A2. The 2nd argument to check_args() is a string, with (usually) 1 character per expected argument to the method being processed. For example, in Polysurface, the InsertPoly() method takes a list of vertices, so check_args() needs to check that a single argument is being passed from scheme, and that it is a list. The string "l" (l for list) causes check_args() to make this check. Thus "l" indicates that there should be a single argument and that it should be a list, "ll" would indicate two arguments that are both lists, etc. Other argument types and the characters that should be used are "s" for string, "i" for integer, "f" for floating point number, "o" for any Jabka object (i.e. a Glob or one of its subclasses), "t" for transform, "b" for boolean. You can look at some of the other methods to see other complete examples. If the expected argument is to be a list, you may want to be specific about the number of elements in the list. To do this, the letter lmay be followed by a non-zero positive integer indicating the number of elements that the list must contain. Thus "l" means any list is acceptable, but "l3" means that only a 3 element list is acceptable. Finally, the entire string of arguments may optionally end with a *, indicating that one or more additional arguments may be present. For example "ii" means that there must be exactly two integer arguments, but "ii*" means that there must be two integer arguments but that they may be followed by any number of additional un-type-checked arguments.
Q3. In writing the C++ code for the scheme interface to a method, I'm not to sure how to return a list that is of dynamic length. I've looked at other objects like BezPatch (GetPatch()method) to see how they do it, but all of them seem to return a list of static length using the list() function. It seems you have to make 1 call to list() with a terminating NULL. If you have a list of dynamic length then you can't make just 1 call to list(). Is there a way to return a dynamic length list of verticies or do we have to come up with our own method?
A3. You'll need to write your own code, but it's easier than you think. You can call scheme procedures like cons directly from your C++ code. Here is a rough sketch of how to do it. Assume that outline is an array of n Vector3D's containing a polyline that is to be returned.
LISP slist; // temporary scheme structure to hold the list
slist = NIL; // NIL is C constant for the empty list
for(i = n - 1, i >= 0; i--)
slist = cons(V3(outline[i]), slist); // V3 converts vector to 3 elt lst
return(slist);
Q4. What is the purpose of the InitializeDefaults() method in each class? It sounds obvious, but did not produce the results I expected. It seems that this function is called once for each object when Jabka begins but not when a new object is created. I have some pointers to ints which I had first malloced in InitializeDefaults(), but when I tried to create a new object and use these int*'s they were pointing to NULL as if they had not been malloced. I had to move the mallocs into the methods to get it to work.
A4. InitializeDefaults() is run once per object class, on Jabka system startup, when the initial prototype objects are created for each class. For each class (like Material) there is one object created whose name corresponds to the class (material). This object establishes the defaults for the class, and so needs to have all of its instance variables set to the default values intended by the designer of the object (you). You specify these defaults in InitializeDefaults(). Each class constructor, like Material(), should set all of the instance variables (remember, they are always pointers) to NULL. Later, when a new object foo is created using this prototype: (object foo material), foo's instance variables will be NULL unless and until explicitly set (like by SetType). Each Get...() method must check the instance variable to see if it is NULL. If not NULL, then it can simply return the value pointed to by the instance variable (*ivarname). If it is NULL, it should, in turn, call the identical Get...() method on the object's prototype (prototype->Get...()). Set...() methods each need to malloc space for the variable's contents if the variable is NULL, otherwise it can usually store into the space currently pointed to by the instance variable. See also BezPatch.C GetApproxLevel() and SetApproxLevel() methods for a simple example showing all of the details.
Q5. It's confusing and annoying to have to check all instance variables for NULL in each and every method that uses them. Is there an easier way?
A5. The simple answer to this is never access your instance variables directly! Always use a Get function to retrieve an instance variable's value, and a Set function to store its value. Then the Get function is the only place where the prototype value is fetched when necessary, and the Set function is the only place where a malloc of space for the instance variable ever has to be done.
Q6. Once I've rendered a scene, how do I save the rendered image to a file?
A6. Assuming that you have rendered to the Image colorimage, you need simply call its save method with a single argument giving the file name to save to. E.g. (colorimage (save "filename")) It gets saved with whatever filename you specify, with the suffix .ppm appended. The file will be in Portable Pixmap (PPM) format, which is viewable by xv and convertable into many other file formats via the PPM utilities. It is also possible to render directly to a file, as you would usually do when rendering offline or for animation production. In this case, simply set the output Device of the Renderer's Image object to a File device, instead of a Pixmap. For example, assuming that the renderer is rendering to the Image colorimage, the following will set things up so that the image is saved to a file on completion of rendering, rather than being displayed on the screen:
(object myfile ppmfile (setfilename "filename"))
(colorimage (setdevice myfile))
Q7. I wrote a SweptSurface object that does incremental rotations of an outline, to define a swept polygonal surface. However, I find that by the time the outline is rotated incrementally through a full 360 degrees it no longer exactly matches the original outline. Is there a bug in the Rotate() method of Transform?
A7. You will find that if you do a series of incremental rotations, you will accumulate the small amount of truncation error that occurs on each rotate, and eventually things will distort enough that they will not look right. In your rotated outline example, you could prevent this by accumulating the rotation angle, generating a new rotation through this angle, and rotating the original outline through the total accumulated rotation.