Contents |
|
References
References in MAX, not to be confused with C++ references, are used to keep track of dependencies between scene elements.
The following are excerpts from the help file.
"A reference is a record of dependency between a reference maker and a reference target. The reference maker is said to be dependent upon the reference target. If the target changes in some way that affects the maker, the maker must be notified so it may take appropriate action."
"The system takes care of loading and saving references when an object is saved to disk. An object does not need to explicitly save its references, nor does an object need to load its references. After a scene is loaded, an object's references will automatically be restored."
This is just what we need. In our case the portal is a reference maker and the destination cell node is a reference target. Let's see how to use references in our plug-in.
If we want to create references to anything in our plug-in class we have to derive it from the MAX SDK class ReferenceMaker. We are currently deriving from Animatable but since ReferenceMaker is derived from Animatable as well we'll just subclass PortalObj off ReferenceMaker instead. We're moving on up in the class hierarchy.
For a scene element to be a reference target, it must be derived from the class ReferenceTarget. That's OK too, because nodes are an instance of the INode class which is subclassed off ReferenceTarget. You can look at the class hierarchy diagram now to get your bearings if you haven't already done it.
We still need to store pointers to the source and destination cells but we'll be using references to manage them. We begin by declaring class variables.
INode *src, *dest;
Each pointer will be associated with a reference and we'll use ID numbers to distinguish between them. How we will use these IDs will become clear very soon. So we next define IDs.
#define
ID_SRC_REF 0
#define ID_DEST_REF 1
The portal then creates a reference to the node using the function MakeRefByID(). If it's a source node, we pass ID_SRC_REF as a parameter and if it's a destination node we pass ID_DEST_REF. The code looks like this
portal->MakeRefByID(FOREVER, ID_DEST_REF, node);
Notice that FOREVER. It's supposed to indicate the interval of time over which the reference is valid in case you're doing an animated sequence but in MAX 3.1 plug-ins can't pass any other value anyway so we won't worry about it.
When you call MakeRefByID() MAX responds by calling another function that we've inherited from ReferenceMaker, called SetReference(), with a pointer to the reference target so we can store it. Our implementation of SetReference() looks like this.
void
PortalObj::SetReference(int i, RefTargetHandle
rtarg)
{
switch
(i)
{
case
ID_SRC_REF: src = (INode*)rtarg; break;
case
ID_DEST_REF: dest = (INode*)rtarg; break;
}
}
So far we've just taken a rather roundabout route to assign a pointer to a variable. But it's going to be worthwhile because we've inherited another function from ReferenceMaker called NotifyRefChanged(). This will be called by the reference targets whenever they are changed in any way that can affect us and we'll get a pointer to the reference target so we can find out which one was changed and decide what to do about it.. We'll use it to detect if the source or destination node has been deleted and if it has, we'll set the corresponding pointer to NULL.
RefResult
PortalObj::NotifyRefChanged(Interval
changeInt, RefTargetHandle
hTarget,
PartID&
partID,
RefMessage
message)
{
// see if src or dest was
deleted
if (message ==
REFMSG_TARGET_DELETED)
{
if
(hTarget == src) src =
NULL;
else if (hTarget
== dest) dest = NULL;
}
return REF_SUCCEED;
}
Next we need to think about file loading and saving. Actually, it's already done. The system saves all references automatically and when the file is opened again, it'll call SetReference() with pointers to the nodes so our variables get initialized.
To complete the picture, we need to implement two more functions of ReferenceMaker for the system to use. These are NumRefs() and GetReference().
int
PortalObj::NumRefs()
{
return
2;
}
RefTargetHandle PortalObj::GetReference(int
i)
{
switch
(i)
{
case
ID_SRC_REF: return src;
case ID_DEST_REF:
return dest;
}
}
In NumRefs(), should we return the number of references we actually have created already or the maximum number we're planning to create? The answer is: the latter, otherwise problems will occur. This is because the system handles our references as a virtual array that is 0-based like in C++. So valid indexes for the system are 0 to NumRefs() - 1. For example, suppose the user creates a portal, assigns a destination cell only and then saves and closes the file. We've created only one reference, but if we return 1 in NumRefs(), MAX will only care about the 0th reference. It won't call SetReference()with ID_DEST_REF when it opens the file again and our dest variable won't get initialized. If we return 2, on the other hand, our SetReference() will be called only once, with ID_DEST_REF as a parameter and that's how it should be. In any case, SetReference()won't be called with ID_SRC_REF, because the 0th reference doesn't exist.
For the complete low-down on references, look under 'Must Read Sections for All Developers / References'.
Now that we've taken care of the source and destination cell pointers, we can shift our attention to the geometry data. The portal mesh consists of four planar vertices and two faces. To generate this, we'll need to get some information from the user during the creation phase in the viewport. For example, we could store the opposite corners of the rectangle formed by the four vertices. This data is not subject to the problems faced by the pointers so we could simply use class variables without creating any references. For now we'll assume that this is what we're going to do, but there are wheels within wheels, so we'll end up storing the geometry data in a slightly different way.
The Creation Phase in the Viewport
We want to let the user create a portal by clicking and dragging with the mouse in one of the viewports in much the same way as he'd create a box or a sphere. We need to provide MAX with a function to call so that it can let us know what the user is doing with the mouse. MAX gives us information about mouse events like MOUSE_POINT, MOUSE_MOVE, MOUSE_DBLCLICK etc and the position of the mouse pointer. How we use this information is up to us.
To write our mouse handler, we create a class derived from the MAX SDK class CreateMouseCallBack, thereby inheriting a pure virtual function called proc() that we will implement and MAX will call. Let's call this class PortalObjMouseHandler; it's a new class and quite distinct from PortalObj, our main plug-in class. Now how do we let MAX know about PortalObjMouseHandler? It so happens that in the class BaseObject, derived from ReferenceTarget, there is a function called GetCreateMouseCallBack() that MAX calls to find out about the plug-in's mouse handler. So we move up further in the class hierarchy and derive our main plug-in class from BaseObject instead of ReferenceMaker, thereby gaining the ability to handle mouse input along with our existing capabilities of displaying our user interface and creating references. All is well, and we can now return a pointer to an instance of the class PortalObjMouseHandler in our implementation of GetCreateMouseCallBack().
While creating our portal, we'll be working in a coordinate system centered at (0, 0, 0) in the viewport with the +ve X axis to the right, the +ve Y axis upwards and the +ve Z axis coming out of the screen towards us. I don't know what this coordinate system is called in MAX so in this article we'll call it as Csx. Csx is independent of the view chosen for the viewport and is not necessarily aligned with the coordinate system displayed near the bottom left corner. This is why, for example, when creating a teapot we're always looking at it from above in the viewport we are creating it in. The exceptions to this are the User and the Perspective views, in which case the points are in the coordinate system shown in the bottom left corner.
We'll create our portal in the XY plane in a local coordinate system originating at the center of the rectangle. At the same time, we'll fill in a translation matrix that will contain the position of the center point with respect to the Csx origin. MAX will use this matrix to display the portal at the correct position in the viewport. We'll click and hold down the left mouse button to define any one corner of the rectangle, drag, and let go to define the opposite corner. The rectangle will always be created facing us; we can then move and rotate it to position it as desired within the scene. The rectangle parameters we'll store are half the height and half the width and MAX will store the transformation matrix required to display the portal correctly in the scene.
The code for our mouse handler is shown in Listing 1. The fifth parameter, IPoint2 m, is the position of the mouse pointer in Csx and the sixth, Matrix3& mat, is the matrix we set to the initial translation required to display the portal at the correct position in the viewport. There's also some stuff related to viewport snapping. For details, look for the description of the class CreateMouseCallBack in the help file. Remember that to find a method in the help file, you've got to type 'methods, MethodName' and to find a class you've got to type 'class ClassName'
________________________________________________________