Contents |
|
A Matter of Class
So far, we've been working our way up the class hierarchy based on what functionality we wanted to add to our plug-in. We started with Animatable and have now reached BaseObject after a brief halt at ReferenceMaker. I guess it's time I came clean and told you that we're got some way to go still. We'll be deriving our main plug-in class from the MAX SDK class SimpleObject. Why? Mainly because the help file says so and that's the way it's done. We started at the base of the class hierarchy; actually you'd do just the opposite. You'd start by identifying what class you need to derive from based on your plug-in's type and then see what methods of the base classes you need to implement as well. The two places in the help file that serve as starting points for your journey are 'Plug-In Types Overview' and 'How To Create Various Plug-In Types'. At least for me, this is followed by a lot of relentless clicking as I wade through the documentation until light starts to dawn. The samples are another valuable source of information. For this plug-in, I started with Sphere_c in the Samples/HowTo directory. That's a comprehensive sample with around 1500 lines of code so I kept cutting out stuff until I got a working sample that was small enough for me to comprehend.
We need SimpleObject because, as the help file says, "Procedural object that represent themselves with a mesh may use this class. It provides implementations of many of the methods required to create a procedural object thus simplifying the developer job considerably."
Before we get on to creating the mesh and picking the source and destination nodes, we'll get back to the issue of storing our geometry data. Recall that at the end of the section on references, we said that we'd store this data in a different way because of certain wheels within wheels. Well, these wheels belong to SimpleObject. Enter Parameter Blocks.
Parameter Blocks
Parameter blocks are not really required for the simple plug-in we are writing. However they will surely crop up, especially in conjunction with Parameter Maps, when we move on to more complex applications so it won't hurt to explore this topic now, especially as they're quite simple to understand and use.
The parameter block is a mechanism for storing the values of a plug-in's parameters. We first create a description of the parameter block we want to create. This description consists of information about the number of data items we want to store and their types. We get to choose from a set of built-in types such as int, float and 3D vector. When we create the block using this description, MAX allocates memory for the data elements and we access individual items using indexes.
We'll use a parameter block to store our geometry data. It consists of two floating-point values, the half-height and the half-width. To describe the parameter block, we need an array of ParamBlockDesc objects, one for each data item. For each item, we first specify the type. The next value is always NULL and the third indicates if the item is animatable. Our parameter block looks like this.
static
ParamBlockDesc pblockDesc=
{
{
TYPE_FLOAT, NULL, FALSE }, // index 0;
half-height
{ TYPE_FLOAT, NULL, FALSE } //
index 1; half-width
};
We create the parameter block using the CreateParameterBlock() function. We'll use the first element in the parameter block for the half-height and the second for the half-width. We can access our data by ID using the SetValue() and GetValue() functions. For a description of these as well as more information on Parameter Blocks check out 'Must Read Sections for All Developers / Parameter Blocks'.
The main
reason we are using a parameter block is that SimpleObject expects us to.
It has a variable, IparamBlock *pblock, that we've inherited. Now,
BaseObject has a virtual GetParamBlock() function that the system will
call to ask the plug-in for it's parameter block and the default
implementation is to return NULL; SimpleObject overrides this and returns
pblock. The problem with this setup is that pblock is a wild pointer that
was not set to NULL by SimpleObject in it's constructor. The system thinks
that it's got a valid pointer to our parameter block and presumably
proceeds to use it which that brings us to a grinding halt. You can see
the code for the class SimpleObject in simpobj.h in the SDK Include
directory and simpobj.cpp in the Samples/ HowTo/Misc directory.
Of
course, we can still keep our geometry data in class variables and not
create a parameter block. All we have to do to prevent a crash is to set
pblock to NULL in the PortalObj constructor. But since we've come so far
we might as well continue. The parameter block class, IparamBlock, is
derived from ReferenceTarget, so we can create a reference to it. This is
again for the benefit of SimpleObject, which is waiting with it's
implementation of the NumRefs(), GetReference(), SetReference() and
NotifyRefChanged(). The SimpleObject version of the first three is given
below.
int NumRefs() {return 1;}
RefTargetHandle GetReference(int i) {return pblock;}
void SetReference(int i, RefTargetHandle rtarg) {pblock=(IParamBlock*)rtarg;}
Note that we now need to call the SimpleObject versions in our implementation of NumRefs(), GetReference(), SetReference() and NotifyRefChanged(). We also need a reference ID for the reference to the parameter block.
Building the Mesh
We're now ready to build our mesh. This consists of filling in the BuildMesh() function that we've inherited from SimpleObject. We've also inherited the variable mesh that's an instance of the Mesh class. If you look at a description of this class in the help file you'll see quite a bit of stuff there, but we'll just initialize the vertex list and the face list.
Let's have a quick look at how the mesh is organized. Basically, it's a collection of faces with counter-clockwise winding. Each face has three vertices, each of which can have upto three elements of information, the 3D space coordinates, the texture coordinates and the vertex color. This information is stored in three separate arrays which are Point3 *verts, UVVert *tVerts and VertColor *vertCol respectively. To index into these, there are three arrays of Face objects, the class members Face *faces, TVFace *tvFace and TVFace *vcFace respectively.
So, to get all the information about the ith face in a mesh, we proceed as follows. The vertex coordinates are verts.y, and verts.z. For a 2D UV texture mapping, the texture coordinates are tverts.y. Finally the vertex colors are vertCol.y and vertCol.z.
The code that incorporates all that we've discussed so far is given in Version3.cpp. The new features are that we're deriving our class from SimpleObject, we're building the mesh, we've got references, the parameter block, the mouse handler and last but not least, it doesn't crash any more.
________________________________________________________