Contents

Preliminaries

References

A Matter of Class

Node Picking

Node Picking

Next we'll see how to pick nodes for the source and destination cells. The first step is to set the command mode to a standard pick mode. For example, if we create a box, select it, go to the Modify branch in the command panel, click on Edit Mesh and set the Selection Level to Vertex, we're in pick mode. To get to this mode, we need to call the MAX function SetPickMode(). We got a pointer to the class IObjParam in our BeginEditParams() method and we saved in the class variable ip. We can use this to call functions that are exported from the MAX executable; AddRollupPage() is one of them, SetPickMode() is another. To get an idea of what else can be done with this pointer, look up the class Interface in the help file. IobjParam is identical to this class.

Once we're in pick mode, MAX needs a function to call to let us know which node the user is clicking on. We had a similar situation when we were writing the mouse handler. To create a callback for node picking, we derive a class from the MAX SDK class PickModeCallback and pass a pointer to an instance of the derived class when we call SetPickMode(). We'll call this class PortalObjPickModeCallback. In this class, we have two inherited functions to implement: HitTest()and Pick().

HitTest() gets called when the user clicks with the mouse in one of the viewports. The system passes it a bunch of parameters like the viewport and the mouse position based on which we have to find out if a node was hit. Luckily for us, we can call the Pick() method of class Interface that will hit test the screen position for nodes and return an INode pointer if one was hit or NULL otherwise.

If HitTest() returns TRUE indicating that a node was selected, our Pick()method gets called. Here we find which node was selected, create a reference to that node, display it's name in the edit control and end the pick mode by returning TRUE.

The code that handles the node picking is given in Listing 2. Version4.cpp contains this code plus Version3.cpp. If you build it, you can see what is close to the finished version of the plug-in. A couple of problems still need sorting out, however. The first is that when you pick a node as the source or destination cell in the create pane of the command panel, the rollup page disappears and we find ourselves in the Move mode. For now, select the portal and go to the modifiers pane to pick the source and destination cells. Another problem is that any node can be chosen as the source or destination; a light, a camera or even the portal itself. Recall that we'd decided to put in a couple of checks to make sure that only boxes were assigned as source or destination cells. We'll get to that in the next section where we'll descend down the geometry pipeline into the murky depths of MAX.

A Short Trip Down the Geometry Pipeline

This is a complicated topic that I don't claim to know too well myself so we'll just have a brief overview here. The geometry pipeline system is what allows a node in the scene to be altered, perhaps repeatedly, through the application of modifiers. To see this in action, create a portal, choose the Edit Mesh modifier, set the Selection Level to Vertex, click on a corner of the portal and drag it to deform the portal. How is this deformation happening? After all, we didn't take care of this kind of thing in our mesh building code. What actually happened was this: when we created our portal, MAX created a node for it and it became the Base Object at the beginning of the pipeline. That's where it's still exists unaltered. To verify this, click on the Edit Stack button in the Modifier Stack rollup page and you'll see that the Edit Mesh modifier has been added to our node's pipeline. If you now delete the modifier by selecting it and saying Cut, you can see the portal in it's pristine form. Alternatively, you can say Collapse all, in which case the base object and the results of applying the modifier will get fused into one giving us an Editable Mesh. In that case we can say goodbye to the portal as we knew it cause we'll never see it again.

What we see in the viewport and what gets rendered is the result of evaluating the entire pipeline with the output of each stage being the input to the next. The final result is called the world space state of the object. Let's see how we'll use this information. In our plug-in, we need to check if a given node is a box simply by seeing if it has eight vertices and twelve faces. This test is rather primitive but will do for now. The first step is to evaluate the node's pipeline and get it's world space state. This is done by calling the node's EvalWorldState() function to get an instance of the class ObjectState which contains a pointer to the object that appears in the scene. To make sure the node's not a light or a camera, we check to see if it can be converted to a triangle mesh by calling the object's CanConvertToType() method. If it can be converted, we ask it do so, after which we can access the mesh and count the number of vertices and faces it has. The code that does this is given in Listing 3.

There is a detailed discussion of the geometry pipeline under 'Must Read Sections for All Developers / Geometry Pipeline System'. You can read up on nodes and object modification in the Advanced Topics section. I found it heavy going on the whole but the good part is that we don't really need to know too much to be able to get our job done. In fact the little we've discussed will take us far. For example, we'll see in the next section this is about all the information we need on this topic to export geometry from a scene.

Finally, let's see what to do about the other problem that we had, the disappearing rollup page. It says in the documentation for the PickModeCallback::Pick() method that returning TRUE will set the command mode to MOVE so I don't see that we can do much about it. Instead, we'll cunningly remove the rollup page from the Create pane altogether and ensure that it only appears on the Modifier pane. If you look at the documentation for the function Animatable::BeginEditParams(), you'll see that the flags parameter indicates which branch of the command panel the user is in and none of the given values is for the Modify branch. So we'll create a rollup page only if flags is zero.

The latest and greatest version of the plug-in code with the nodes check and the sleazy hack for the rollup page is given in Version5.cpp. That's our procedural object plug-in.

What's Next

How do we export our procedural object? We'd have to write an export plug-in for that and it would take too long to discuss the procedure in detail here, but we a quick overview is possible. To write an export plug-in, we need to derive our main plug-in class from the MAX SDK class SceneExport and implement a bunch of inherited pure virtual functions. One of these is DoExport(), which is called when the user asks to export the scene into our file format. DoExport() has a parameter that is a pointer to an instance of the class ExpInterface, derived from IScene, which can be used to enumerate all the nodes in the scene. The function to do this is IScene::EnumTree(). We create an enumeration callback which is called for every node in the scene. Inside this callback, we need to see if the node we got is a portal and, if so, proceed to export it. One simple way of doing this is to see if it's name begins with the string "Portal". If it does, we can convert it to a TriObject and get the geometry data. Next we need to get the source and destination cell pointers. For that we get the pointer to the object in the scene that the node references and typecast it to a pointer to an instance of the PortalObj class, after which we can access the class variables src and dest. Part of the code for the callback is shown below.

int NodesCallback: callback(INode *node)
{
    ObjectState os;
    Object *obj;
    TriObject *tri;

    // evaluate node state
    os = node->EvalWorldState(0);
    obj = os.obj;

    // check node name
    if (_strnicmp("Portal", node->GetName(), 6))
        return TREE_CONTINUE;

    if (obj->CanConvertToType(Class_ID
                              (TRIOBJ_CLASS_ID,0)))
    {
        tri = (TriObject *) obj->ConvertToType(0,
            Class_ID(TRIOBJ_CLASS_ID, 0));

        // export the geometry data here

        // get a PortalObj pointer to access the source         // and destination cells
        PortalObj *portal =             (PortalObj*)node->GetObjectRef();

        // export source and destination cells here

        if (obj != tri) tri->DeleteMe();
    }

    return TREE_CONTINUE;
}

When exporting the geometry, remember that the vertex coordinates will be in the local coordinate system. The matrix to convert them to world coordinates can be obtained by calling the node's GetObjectTM() function. There's also a INode::GetNodeTM(), but that returns the transformation matrix for the node's pivot, because in MAX the node's pivot and the geometry can be moved around independently of each other.

You might want to add another rollup page with spinner and edit controls so that the user can interactively adjust the creation parameters. This can be done using MAX's custom controls and Parameter Maps. The documentation can be found under 'Must Read Sections for All Developers / Custom User Interface Controls and Must Read Sections for All Developers /Parameter Maps'.

Gurjeet Sidhu is the lead programmer at Dhruva Interactive which is a company in India a rare and unique organism in Indiaas there aren't may game teams in India. Gurjeet has been doing graphics programming for about 4 years now and spent most of last year working on the port of Infogrammes title Mission Impossible from the N64 to the PC; unfortunately that got scrapped at a pretty advanced stage. Right now, the main purpose of his existence is to investigate culling schemes and decide what he'll use for the next version of our game engine. You can reach Gurjeet at mailto:gx@dhruva.com

________________________________________________________

[Back to] Preliminaries