Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
89 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Introduction
 Modifiers
 Utilities
 Using MFC
 Conclusion

 Get the project
 Printable version

 


Advantages of Developing a Utility

Utility plug-ins are easier to develop than modifiers; you don't need to understand a complex pipeline to develop a utility. Only a few basics of 3D Studio Max programming are even necessary really, and you generally only need to know where in the scene you can find the information that you need (although even that can become a headache!) For example, to get the number of selected nodes as well as their pointers you would use the following code:

// Get Interface pointer, Interface is the most useful // class in 3D Studio Max Interface* ip = GetCOREInterface(); if ( ! ip ) return; // Get the number of selected nodes in NumSelNodes const int NumSelNodes = ip->GetSelNodeCount(); // Now scan all the selected nodes one at a time for (int i = 0 ; i < NumSelNodes; i++) { INode* node = ip->GetSelNode( i ); if (!node) continue; DoSomethingOnThisNode(node); }

Let's have a quick overview of materials and the material editor in 3D Studio Max and how these materials are used for real time games. The material editor is used to manage the materials. It can create materials such as Blend, Composite, Double sided, Matte / Shadow, Morpher, Multi / SubObject, RayTrace, Shellac, Standard, Top / Bottom, etc.

For real time games, we only use the Standard and Multi/Subobject materials. Standard materials have properties such as ambient color, diffuse color, specular color, glossiness, opacity, bump, etc. For real time games, we set a bitmap texture in the diffuse channel of a material. (In other words, we replace the overall color with an overall texture.)

In 3D Studio Max, a node can reference one - and only one - material, any kind of material previously described. There is not (as in Alias|Wavefront Maya) the possibility to set a different material for each face. That's why the Multi/Subobject materials exist! When you want an object to have several different materials applied to individual parts of the object, you use a Multi/Sub-object material, which is a material that holds a number of different materials (called submaterials ) inside it.

We usually create a multi/subobject material that contains standard materials with each a bitmap texture in the diffuse channel. So how can we now set the good texture on a part of a mesh from this multimaterial?

Each face of the mesh has an integer which is the submaterial number in the node's multi/subobject material called the face material ID. It maintains the identification with the submaterial within the multi/subobject material. So when a multimaterial is applied on a node, if the faces from 0 to 100 have the face material ID 0, they reference the first submaterial (so it references the bitmap texture contained in this submaterial); if faces 101 to 200 have the face material ID 1, they reference the second submaterial; and so forth. When a standard material is applied on a mesh, these face material IDs are not used.

Now to get all materials that are in your scene, you can use the following code ("In the scene" means that all these materials are applied to at least one of the nodes in the scene):

//Get Interface pointer, Interface is the most useful // class of 3D Studio Max Interface* ip = GetCOREInterface(); if ( ! ip ) return; //Get all materials stored in a library of materials //The MtlBaseLib class is a library of MtlBase entries. MtlBaseLib* lib = ip->GetSceneMtls(); if (!lib) return; const int NumMat = lib->Count(); for( int i = 0; i < NumMat; i++ ){ MtlBase* mtl = static_cast<MtlBase*>( (*lib)[ i ] ); if ( ! mtl) continue; DoSomethingOnThisMat( mtl ); }

One advantage of using a utility over a modifier plug-in is that you can use several utilities at the same time if they have modeless dialog boxes instead of dialog boxes set in the 3D Studio Max rollup panel. This is not possible with modifiers, where the active modifier is the one which is selected in the modifier stack, but two modifiers can't be active at the same time.

Another advantage is that you can drive modifiers from a utility. Let's see how to instantiate modifiers in 3D Studio Max.

First, you can't use the new operator to instantiate all the classes you need in 3D Studio Max. It will work for some classes but most of them are not instantiable - for example the modifiers classes. The 3D Studio Max system uses unique class Ids (a unique set of 2 32-bits wide integers) to identify each class. So to instantiate a class you have to use the CreateInstance function from the class interface . You pass the CreateInstance function a class ID and it returns a pointer to an instance of the desired class. This is a class factory design pattern that we can find as well in the Microsoft Component Object Model (COM) with the CoCreateInstance function.

In our case, if we have the Edit Mesh and UVW Unwrap modifiers class IDs, we can create instances of these modifiers. And we are able to apply them programmatically on some objects in the scene (get the project).

Example 1: How to create an instance of the UVW Unwrap modifier

#ifndef UNWRAP_CLASSID #define UNWRAP_CLASSID Class_ID(0x02df2e3a,0x72ba4e1f) #endif Interface* ip = GetCOREInterface(); if ( ! ip ) return; Modifier* UVWUnwrapMod =static_cast<Modifier*> (ip->CreateInstance(OSM_CLASS_ID, UNWRAP_CLASSID));

But now that you are able to add through code modifiers to some nodes, you'd surely like to use their functions, right? This is where things become complicated. As you've probably already seen, the C++ projects generated by the 3D Studio Max wizard have the same structure: the classes are declared in the same file as their implementation in the cpp file. It's a pity that header files don't contain class definitions, because it would be so simple to include the header file of the class you need (in our case a modifier) in your project settings and link with the lib file from the class project, as we usually do when using a library.

But it's not possible to include the header files with the 3D Studio Max modifiers classes, so here's the workaround I've found. It may well not be not the best way to do this, and I imagine I'll receive a flood of e-mails suggesting better methods, but if you do need one, this works fine. It's actually just a copy and paste operation; I copy the class definitions from the original modifier cpp file in a header file in my project, then I copy all the functions of the class that I want to use (and their dependencies) in a cpp file in my project. Then I add my own functions in the class definition to extend this class.

I was then able to call functions from the modifier. As I said, there are probably much better solutions, but as usual, I only had a limited time to reach my goal so I had to make it work in any way I could! And it does work; I succeeded in driving the Edit Mesh, the UVW Map and the UVW Unwrap modifiers from a utility plug-in.

(Notably, most of these problems concerning the header files will be solved in the future, because 3D Studio Max is becoming an open source project. See http://www2.discreet.com/games/developers_corner.html for more information.)

Drawbacks of Developing a Utility

The big drawback of utilities is that when you modify an object in a utility, say, by changing the face selection set, the changes don't "stay forever." Once the viewport is redrawn, the geometry pipeline is re-evaluated (as previously described), and your changes are lost. To make your changes "stay forever," you'll need to collapse the modifier stack (HTML Link on the project). This is the main constraint that you have to keep in mind when developing a utility plug-in: modifications are only allowed on collapsed objects (objects that have their modifier stack collapsed). And sometimes the artists don't want you to collapse the modifier stack of their objects because if you do so, they won't be able to go back into the modifier stack history.

Let's introduce the Physique and Skin modifiers. They are used to assign bones to some vertices of a mesh to animate it and use inverse kinematics. Physique is designed for biped character animation; it has a predefined biped skeleton with 2 arms, 2 legs, 2 feet, a head, etc. Skin is designed to add bones to any part of a mesh; there is no predefined skeleton. If Skin or Physique modifiers are applied on a node, this node can't have its modifier stack collapsed - or all the bones-to-vertices assignation will be lost! (If a node has a modifier that stores its own information and this information can't be stored in the mesh, the modifier stack of this node can't be collapsed. Well, theoretically, it can be collapsed… it won't crash, but all your information will be lost!)




Next : Using MFC